diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 44e21f7e2..de123e13f 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -129,6 +129,7 @@ name = "bitcoin-units" version = "0.2.0" dependencies = [ "arbitrary", + "bincode", "bitcoin-internals", "serde", "serde_json", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 00ef2b11c..b9ed4db00 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -128,6 +128,7 @@ name = "bitcoin-units" version = "0.2.0" dependencies = [ "arbitrary", + "bincode", "bitcoin-internals", "serde", "serde_json", diff --git a/api/units/all-features.txt b/api/units/all-features.txt index f084694c5..08c1a2ce0 100644 --- a/api/units/all-features.txt +++ b/api/units/all-features.txt @@ -3,6 +3,7 @@ #[non_exhaustive] pub struct bitcoin_units::amount::MissingDenominationError #[non_exhaustive] pub struct bitcoin_units::amount::PossiblyConfusingDenominationError(_) #[non_exhaustive] pub struct bitcoin_units::amount::UnknownDenominationError(_) +#[non_exhaustive] pub struct bitcoin_units::fee_rate::serde::OverflowError #[non_exhaustive] pub struct bitcoin_units::locktime::absolute::ConversionError #[non_exhaustive] pub struct bitcoin_units::parse::ParseIntError impl bitcoin_units::Amount @@ -52,6 +53,7 @@ impl core::clone::Clone for bitcoin_units::block::BlockHeight impl core::clone::Clone for bitcoin_units::block::BlockInterval impl core::clone::Clone for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::clone::Clone for bitcoin_units::fee_rate::FeeRate +impl core::clone::Clone for bitcoin_units::fee_rate::serde::OverflowError impl core::clone::Clone for bitcoin_units::locktime::absolute::ConversionError impl core::clone::Clone for bitcoin_units::locktime::absolute::Height impl core::clone::Clone for bitcoin_units::locktime::absolute::ParseHeightError @@ -82,6 +84,7 @@ impl core::cmp::Eq for bitcoin_units::block::BlockHeight impl core::cmp::Eq for bitcoin_units::block::BlockInterval impl core::cmp::Eq for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::cmp::Eq for bitcoin_units::fee_rate::FeeRate +impl core::cmp::Eq for bitcoin_units::fee_rate::serde::OverflowError impl core::cmp::Eq for bitcoin_units::locktime::absolute::ConversionError impl core::cmp::Eq for bitcoin_units::locktime::absolute::Height impl core::cmp::Eq for bitcoin_units::locktime::absolute::ParseHeightError @@ -122,6 +125,7 @@ impl core::cmp::PartialEq for bitcoin_units::block::BlockHeight impl core::cmp::PartialEq for bitcoin_units::block::BlockInterval impl core::cmp::PartialEq for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::cmp::PartialEq for bitcoin_units::fee_rate::FeeRate +impl core::cmp::PartialEq for bitcoin_units::fee_rate::serde::OverflowError impl core::cmp::PartialEq for bitcoin_units::locktime::absolute::ConversionError impl core::cmp::PartialEq for bitcoin_units::locktime::absolute::Height impl core::cmp::PartialEq for bitcoin_units::locktime::absolute::ParseHeightError @@ -170,6 +174,7 @@ impl core::convert::From for u64 impl core::convert::From for bitcoin_units::amount::ParseAmountError impl core::convert::From for bitcoin_units::amount::ParseDenominationError impl core::convert::From for bitcoin_units::amount::ParseError +impl core::convert::From for bitcoin_units::fee_rate::serde::OverflowError impl core::convert::From for bitcoin_units::parse::PrefixedHexError impl core::convert::From for bitcoin_units::parse::UnprefixedHexError impl core::convert::From for bitcoin_units::locktime::relative::Height @@ -218,6 +223,7 @@ impl core::error::Error for bitcoin_units::amount::PossiblyConfusingDenomination impl core::error::Error for bitcoin_units::amount::TooPreciseError impl core::error::Error for bitcoin_units::amount::UnknownDenominationError impl core::error::Error for bitcoin_units::block::TooBigForRelativeBlockHeightError +impl core::error::Error for bitcoin_units::fee_rate::serde::OverflowError impl core::error::Error for bitcoin_units::locktime::absolute::ConversionError impl core::error::Error for bitcoin_units::locktime::absolute::ParseHeightError impl core::error::Error for bitcoin_units::locktime::absolute::ParseTimeError @@ -244,6 +250,7 @@ impl core::fmt::Debug for bitcoin_units::block::BlockHeight impl core::fmt::Debug for bitcoin_units::block::BlockInterval impl core::fmt::Debug for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::fmt::Debug for bitcoin_units::fee_rate::FeeRate +impl core::fmt::Debug for bitcoin_units::fee_rate::serde::OverflowError impl core::fmt::Debug for bitcoin_units::locktime::absolute::ConversionError impl core::fmt::Debug for bitcoin_units::locktime::absolute::Height impl core::fmt::Debug for bitcoin_units::locktime::absolute::ParseHeightError @@ -274,6 +281,7 @@ impl core::fmt::Display for bitcoin_units::block::BlockHeight impl core::fmt::Display for bitcoin_units::block::BlockInterval impl core::fmt::Display for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::fmt::Display for bitcoin_units::fee_rate::FeeRate +impl core::fmt::Display for bitcoin_units::fee_rate::serde::OverflowError impl core::fmt::Display for bitcoin_units::locktime::absolute::ConversionError impl core::fmt::Display for bitcoin_units::locktime::absolute::Height impl core::fmt::Display for bitcoin_units::locktime::absolute::ParseHeightError @@ -333,6 +341,7 @@ impl core::marker::Freeze for bitcoin_units::block::BlockHeight impl core::marker::Freeze for bitcoin_units::block::BlockInterval impl core::marker::Freeze for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::marker::Freeze for bitcoin_units::fee_rate::FeeRate +impl core::marker::Freeze for bitcoin_units::fee_rate::serde::OverflowError impl core::marker::Freeze for bitcoin_units::locktime::absolute::ConversionError impl core::marker::Freeze for bitcoin_units::locktime::absolute::Height impl core::marker::Freeze for bitcoin_units::locktime::absolute::ParseHeightError @@ -364,6 +373,7 @@ impl core::marker::Send for bitcoin_units::block::BlockHeight impl core::marker::Send for bitcoin_units::block::BlockInterval impl core::marker::Send for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::marker::Send for bitcoin_units::fee_rate::FeeRate +impl core::marker::Send for bitcoin_units::fee_rate::serde::OverflowError impl core::marker::Send for bitcoin_units::locktime::absolute::ConversionError impl core::marker::Send for bitcoin_units::locktime::absolute::Height impl core::marker::Send for bitcoin_units::locktime::absolute::ParseHeightError @@ -394,6 +404,7 @@ impl core::marker::StructuralPartialEq for bitcoin_units::block::BlockHeight impl core::marker::StructuralPartialEq for bitcoin_units::block::BlockInterval impl core::marker::StructuralPartialEq for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::marker::StructuralPartialEq for bitcoin_units::fee_rate::FeeRate +impl core::marker::StructuralPartialEq for bitcoin_units::fee_rate::serde::OverflowError impl core::marker::StructuralPartialEq for bitcoin_units::locktime::absolute::ConversionError impl core::marker::StructuralPartialEq for bitcoin_units::locktime::absolute::Height impl core::marker::StructuralPartialEq for bitcoin_units::locktime::absolute::ParseHeightError @@ -425,6 +436,7 @@ impl core::marker::Sync for bitcoin_units::block::BlockHeight impl core::marker::Sync for bitcoin_units::block::BlockInterval impl core::marker::Sync for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::marker::Sync for bitcoin_units::fee_rate::FeeRate +impl core::marker::Sync for bitcoin_units::fee_rate::serde::OverflowError impl core::marker::Sync for bitcoin_units::locktime::absolute::ConversionError impl core::marker::Sync for bitcoin_units::locktime::absolute::Height impl core::marker::Sync for bitcoin_units::locktime::absolute::ParseHeightError @@ -456,6 +468,7 @@ impl core::marker::Unpin for bitcoin_units::block::BlockHeight impl core::marker::Unpin for bitcoin_units::block::BlockInterval impl core::marker::Unpin for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::marker::Unpin for bitcoin_units::fee_rate::FeeRate +impl core::marker::Unpin for bitcoin_units::fee_rate::serde::OverflowError impl core::marker::Unpin for bitcoin_units::locktime::absolute::ConversionError impl core::marker::Unpin for bitcoin_units::locktime::absolute::Height impl core::marker::Unpin for bitcoin_units::locktime::absolute::ParseHeightError @@ -556,6 +569,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::block::BlockHeig impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::block::BlockInterval impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::fee_rate::FeeRate +impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::fee_rate::serde::OverflowError impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::locktime::absolute::ConversionError impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::locktime::absolute::Height impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::locktime::absolute::ParseHeightError @@ -587,6 +601,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::block::BlockHeight impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::block::BlockInterval impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::block::TooBigForRelativeBlockHeightError impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::fee_rate::FeeRate +impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::fee_rate::serde::OverflowError impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::locktime::absolute::ConversionError impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::locktime::absolute::Height impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::locktime::absolute::ParseHeightError @@ -612,7 +627,6 @@ impl core::str::traits::FromStr for bitcoin_units::locktime::relative::Time impl core::str::traits::FromStr for bitcoin_units::weight::Weight impl serde::ser::Serialize for bitcoin_units::block::BlockHeight impl serde::ser::Serialize for bitcoin_units::block::BlockInterval -impl serde::ser::Serialize for bitcoin_units::fee_rate::FeeRate impl serde::ser::Serialize for bitcoin_units::locktime::absolute::Height impl serde::ser::Serialize for bitcoin_units::locktime::absolute::Time impl serde::ser::Serialize for bitcoin_units::locktime::relative::Height @@ -644,7 +658,6 @@ impl<'a> core::ops::arith::Sub<&'a bitcoin_units::fee_rate::FeeRate> for &bitcoi impl<'a> core::ops::arith::Sub<&'a bitcoin_units::weight::Weight> for &bitcoin_units::weight::Weight impl<'de> serde::de::Deserialize<'de> for bitcoin_units::block::BlockHeight impl<'de> serde::de::Deserialize<'de> for bitcoin_units::block::BlockInterval -impl<'de> serde::de::Deserialize<'de> for bitcoin_units::fee_rate::FeeRate impl<'de> serde::de::Deserialize<'de> for bitcoin_units::locktime::absolute::Height impl<'de> serde::de::Deserialize<'de> for bitcoin_units::locktime::absolute::Time impl<'de> serde::de::Deserialize<'de> for bitcoin_units::locktime::relative::Height @@ -1034,7 +1047,6 @@ pub fn bitcoin_units::fee_rate::FeeRate::add_assign(&mut self, rhs: bitcoin_unit pub fn bitcoin_units::fee_rate::FeeRate::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result pub fn bitcoin_units::fee_rate::FeeRate::clone(&self) -> bitcoin_units::fee_rate::FeeRate pub fn bitcoin_units::fee_rate::FeeRate::cmp(&self, other: &bitcoin_units::fee_rate::FeeRate) -> core::cmp::Ordering -pub fn bitcoin_units::fee_rate::FeeRate::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> pub fn bitcoin_units::fee_rate::FeeRate::eq(&self, other: &bitcoin_units::fee_rate::FeeRate) -> bool pub fn bitcoin_units::fee_rate::FeeRate::fee_vb(self, vb: u64) -> core::option::Option pub fn bitcoin_units::fee_rate::FeeRate::fee_wu(self, weight: bitcoin_units::weight::Weight) -> core::option::Option @@ -1044,7 +1056,6 @@ pub fn bitcoin_units::fee_rate::FeeRate::from_str(s: &str) -> core::result::Resu pub fn bitcoin_units::fee_rate::FeeRate::hash<__H: core::hash::Hasher>(&self, state: &mut __H) pub fn bitcoin_units::fee_rate::FeeRate::mul(self, rhs: bitcoin_units::weight::Weight) -> Self::Output pub fn bitcoin_units::fee_rate::FeeRate::partial_cmp(&self, other: &bitcoin_units::fee_rate::FeeRate) -> core::option::Option -pub fn bitcoin_units::fee_rate::FeeRate::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer pub fn bitcoin_units::fee_rate::FeeRate::sub(self, rhs: &bitcoin_units::fee_rate::FeeRate) -> Self::Output pub fn bitcoin_units::fee_rate::FeeRate::sub(self, rhs: bitcoin_units::fee_rate::FeeRate) -> Self::Output pub fn bitcoin_units::fee_rate::FeeRate::sub_assign(&mut self, rhs: &bitcoin_units::fee_rate::FeeRate) @@ -1054,6 +1065,22 @@ pub fn bitcoin_units::fee_rate::FeeRate::sum(iter: I) -> Self where I: core:: pub fn bitcoin_units::fee_rate::FeeRate::try_from(s: &str) -> core::result::Result pub fn bitcoin_units::fee_rate::FeeRate::try_from(s: alloc::boxed::Box) -> core::result::Result pub fn bitcoin_units::fee_rate::FeeRate::try_from(s: alloc::string::String) -> core::result::Result +pub fn bitcoin_units::fee_rate::serde::OverflowError::clone(&self) -> bitcoin_units::fee_rate::serde::OverflowError +pub fn bitcoin_units::fee_rate::serde::OverflowError::eq(&self, other: &bitcoin_units::fee_rate::serde::OverflowError) -> bool +pub fn bitcoin_units::fee_rate::serde::OverflowError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub fn bitcoin_units::fee_rate::serde::OverflowError::from(never: core::convert::Infallible) -> Self +pub fn bitcoin_units::fee_rate::serde::as_sat_per_kwu::deserialize<'d, D: serde::de::Deserializer<'d>>(d: D) -> core::result::Result::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_kwu::opt::deserialize<'d, D: serde::de::Deserializer<'d>>(d: D) -> core::result::Result, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_kwu::opt::serialize(f: &core::option::Option, s: S) -> core::result::Result<::Ok, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_kwu::serialize(f: &bitcoin_units::fee_rate::FeeRate, s: S) -> core::result::Result<::Ok, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil::deserialize<'d, D: serde::de::Deserializer<'d>>(d: D) -> core::result::Result::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil::opt::deserialize<'d, D: serde::de::Deserializer<'d>>(d: D) -> core::result::Result, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil::opt::serialize(f: &core::option::Option, s: S) -> core::result::Result<::Ok, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil::serialize(f: &bitcoin_units::fee_rate::FeeRate, s: S) -> core::result::Result<::Ok, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_floor::deserialize<'d, D: serde::de::Deserializer<'d>>(d: D) -> core::result::Result::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_floor::opt::deserialize<'d, D: serde::de::Deserializer<'d>>(d: D) -> core::result::Result, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_floor::opt::serialize(f: &core::option::Option, s: S) -> core::result::Result<::Ok, ::Error> +pub fn bitcoin_units::fee_rate::serde::as_sat_per_vb_floor::serialize(f: &bitcoin_units::fee_rate::FeeRate, s: S) -> core::result::Result<::Ok, ::Error> pub fn bitcoin_units::locktime::absolute::ConversionError::clone(&self) -> bitcoin_units::locktime::absolute::ConversionError pub fn bitcoin_units::locktime::absolute::ConversionError::eq(&self, other: &bitcoin_units::locktime::absolute::ConversionError) -> bool pub fn bitcoin_units::locktime::absolute::ConversionError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result @@ -1208,6 +1235,13 @@ pub mod bitcoin_units::amount::serde::as_str pub mod bitcoin_units::amount::serde::as_str::opt pub mod bitcoin_units::block pub mod bitcoin_units::fee_rate +pub mod bitcoin_units::fee_rate::serde +pub mod bitcoin_units::fee_rate::serde::as_sat_per_kwu +pub mod bitcoin_units::fee_rate::serde::as_sat_per_kwu::opt +pub mod bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil +pub mod bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil::opt +pub mod bitcoin_units::fee_rate::serde::as_sat_per_vb_floor +pub mod bitcoin_units::fee_rate::serde::as_sat_per_vb_floor::opt pub mod bitcoin_units::locktime pub mod bitcoin_units::locktime::absolute pub mod bitcoin_units::locktime::relative diff --git a/units/Cargo.toml b/units/Cargo.toml index 021ed1f58..d0368dace 100644 --- a/units/Cargo.toml +++ b/units/Cargo.toml @@ -25,6 +25,7 @@ arbitrary = { version = "1.4", optional = true } [dev-dependencies] internals = { package = "bitcoin-internals", version = "0.4.0", features = ["test-serde"] } +bincode = "1.3.1" serde_test = "1.0" serde_json = "1.0" diff --git a/units/src/fee_rate.rs b/units/src/fee_rate/mod.rs similarity index 98% rename from units/src/fee_rate.rs rename to units/src/fee_rate/mod.rs index 2483bd091..b12c08f45 100644 --- a/units/src/fee_rate.rs +++ b/units/src/fee_rate/mod.rs @@ -2,12 +2,13 @@ //! Implements `FeeRate` and assoctiated features. +#[cfg(feature = "serde")] +pub mod serde; + use core::{fmt, ops}; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use crate::amount::Amount; use crate::weight::Weight; @@ -17,8 +18,6 @@ use crate::weight::Weight; /// This is an integer newtype representing fee rate in `sat/kwu`. It provides protection against mixing /// up the types as well as basic formatting features. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] pub struct FeeRate(u64); impl FeeRate { diff --git a/units/src/fee_rate/serde.rs b/units/src/fee_rate/serde.rs new file mode 100644 index 000000000..281680053 --- /dev/null +++ b/units/src/fee_rate/serde.rs @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: CC0-1.0 + +// Module implements standardized serde-specific trait methods. +#![allow(missing_docs)] +#![allow(clippy::trivially_copy_pass_by_ref)] + +//! 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 serialiation to use. +//! +//! The provided modules can be used as follows: +//! +//! ``` +//! use serde::{Serialize, Deserialize}; +//! use bitcoin_units::FeeRate; +//! +//! #[derive(Serialize, Deserialize)] +//! pub struct Foo { +//! #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_kwu")] +//! pub fee_rate: FeeRate, +//! } +//! ``` + +use core::fmt; + +pub mod as_sat_per_kwu { + //! Serialize and deserialize [`FeeRate`] denominated in satoshis per 1000 weight units. + //! + //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_kwu")]`. + + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use crate::FeeRate; + + pub fn serialize(f: &FeeRate, s: S) -> Result { + u64::serialize(&f.to_sat_per_kwu(), s) + } + + pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result { + Ok(FeeRate::from_sat_per_kwu(u64::deserialize(d)?)) + } + + pub mod opt { + //! Serialize and deserialize [`Option`] denominated in satoshis per 1000 weight units. + //! + //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_kwu::opt")]`. + + use core::fmt; + + use serde::{de, Deserialize, Deserializer, Serializer}; + + use crate::FeeRate; + + pub fn serialize(f: &Option, s: S) -> Result { + match *f { + Some(f) => s.serialize_some(&f.to_sat_per_kwu()), + None => s.serialize_none(), + } + } + + pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result, D::Error> { + struct VisitOpt; + + impl<'de> de::Visitor<'de> for VisitOpt { + type Value = Option; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "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(FeeRate::from_sat_per_kwu(u64::deserialize(d)?))) + } + } + d.deserialize_option(VisitOpt) + } + } +} + +pub mod as_sat_per_vb_floor { + //! Serialize and deserialize [`FeeRate`] denominated in satoshis per virtual byte. + //! + //! When serializing use floor division to convert per kwu to per virtual byte. + //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_vb_floor")]`. + + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use crate::fee_rate::serde::OverflowError; + use crate::fee_rate::FeeRate; + + pub fn serialize(f: &FeeRate, s: S) -> Result { + u64::serialize(&f.to_sat_per_vb_floor(), s) + } + + // Errors on overflow. + pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result { + Ok(FeeRate::from_sat_per_vb(u64::deserialize(d)?) + .ok_or(OverflowError) + .map_err(serde::de::Error::custom)?) + } + + pub mod opt { + //! Serialize and deserialize [`Option`] denominated in satoshis per virtual byte. + //! + //! When serializing use floor division to convert per kwu to per virtual byte. + //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_vb_floor::opt")]`. + + use core::fmt; + + use serde::{de, Deserialize, Deserializer, Serializer}; + + use crate::fee_rate::serde::OverflowError; + use crate::fee_rate::FeeRate; + + pub fn serialize(f: &Option, s: S) -> Result { + match *f { + Some(f) => s.serialize_some(&f.to_sat_per_vb_floor()), + None => s.serialize_none(), + } + } + + pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result, D::Error> { + struct VisitOpt; + + impl<'de> de::Visitor<'de> for VisitOpt { + type Value = Option; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "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( + FeeRate::from_sat_per_vb(u64::deserialize(d)?) + .ok_or(OverflowError) + .map_err(serde::de::Error::custom)?, + )) + } + } + d.deserialize_option(VisitOpt) + } + } +} + +pub mod as_sat_per_vb_ceil { + //! Serialize and deserialize [`FeeRate`] denominated in satoshis per virtual byte. + //! + //! When serializing use ceil division to convert per kwu to per virtual byte. + //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_vb")]`. + + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use crate::fee_rate::serde::OverflowError; + use crate::fee_rate::FeeRate; + + pub fn serialize(f: &FeeRate, s: S) -> Result { + u64::serialize(&f.to_sat_per_vb_ceil(), s) + } + + // Errors on overflow. + pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result { + Ok(FeeRate::from_sat_per_vb(u64::deserialize(d)?) + .ok_or(OverflowError) + .map_err(serde::de::Error::custom)?) + } + + pub mod opt { + //! Serialize and deserialize [`Option`] denominated in satoshis per virtual byte. + //! + //! When serializing use ceil division to convert per kwu to per virtual byte. + //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil::opt")]`. + + use core::fmt; + + use serde::{de, Deserialize, Deserializer, Serializer}; + + use crate::fee_rate::serde::OverflowError; + use crate::fee_rate::FeeRate; + + pub fn serialize(f: &Option, s: S) -> Result { + match *f { + Some(f) => s.serialize_some(&f.to_sat_per_vb_ceil()), + None => s.serialize_none(), + } + } + + pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result, D::Error> { + struct VisitOpt; + + impl<'de> de::Visitor<'de> for VisitOpt { + type Value = Option; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "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( + FeeRate::from_sat_per_vb(u64::deserialize(d)?) + .ok_or(OverflowError) + .map_err(serde::de::Error::custom)?, + )) + } + } + d.deserialize_option(VisitOpt) + } + } +} + +/// Overflow occurred while deserializing fee rate per virtual byte. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct OverflowError; + +internals::impl_from_infallible!(OverflowError); + +impl fmt::Display for OverflowError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "overflow occurred while deserializing fee rate per virtual byte") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for OverflowError {} diff --git a/units/tests/api.rs b/units/tests/api.rs index 373780833..b4dc3aa6e 100644 --- a/units/tests/api.rs +++ b/units/tests/api.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC0-1.0 + //! Test the API surface of `units`. //! //! The point of these tests are to check the API surface as opposed to test the API functionality. diff --git a/units/tests/data/serde_bincode b/units/tests/data/serde_bincode new file mode 100644 index 000000000..51d7dc1d0 Binary files /dev/null and b/units/tests/data/serde_bincode differ diff --git a/units/tests/serde.rs b/units/tests/serde.rs new file mode 100644 index 000000000..d807bcc76 --- /dev/null +++ b/units/tests/serde.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Test the `serde` implementations for types in `units`. + +#![cfg(feature = "alloc")] +#![cfg(feature = "serde")] + +use bincode::serialize; +use bitcoin_units::locktime::{absolute, relative}; +use bitcoin_units::{Amount, BlockHeight, BlockInterval, FeeRate, SignedAmount, Weight}; +use serde::{Deserialize, Serialize}; + +/// A struct that includes all the types that implement or support `serde` traits. +#[derive(Debug, Serialize, Deserialize)] +struct Serde { + #[serde(with = "bitcoin_units::amount::serde::as_sat")] + unsigned_as_sat: Amount, + #[serde(with = "bitcoin_units::amount::serde::as_btc")] + unsigned_as_btc: Amount, + #[serde(with = "bitcoin_units::amount::serde::as_sat::opt")] + unsigned_opt_as_sat: Option, + #[serde(with = "bitcoin_units::amount::serde::as_btc::opt")] + unsigned_opt_as_btc: Option, + + #[serde(with = "bitcoin_units::amount::serde::as_sat")] + signed_as_sat: SignedAmount, + #[serde(with = "bitcoin_units::amount::serde::as_btc")] + signed_as_btc: SignedAmount, + #[serde(with = "bitcoin_units::amount::serde::as_sat::opt")] + signed_opt_as_sat: Option, + #[serde(with = "bitcoin_units::amount::serde::as_btc::opt")] + signed_opt_as_btc: Option, + + #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_vb_floor")] + vb_floor: FeeRate, + #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil")] + vb_ceil: FeeRate, + #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_kwu")] + kwu: FeeRate, + #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_vb_floor::opt")] + opt_vb_floor: Option, + #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_vb_ceil::opt")] + opt_vb_ceil: Option, + #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_kwu::opt")] + opt_kwu: Option, + + a: BlockHeight, + b: BlockInterval, + c: absolute::Height, + d: absolute::Time, + e: relative::Height, + f: relative::Time, + g: Weight, +} + +impl Serde { + /// Constructs an arbitrary instance. + fn new() -> Self { + Self { + unsigned_as_sat: Amount::MAX, + unsigned_as_btc: Amount::MAX, + + unsigned_opt_as_sat: Some(Amount::MAX), + unsigned_opt_as_btc: Some(Amount::MAX), + + signed_as_sat: SignedAmount::MAX, + signed_as_btc: SignedAmount::MAX, + + signed_opt_as_sat: Some(SignedAmount::MAX), + signed_opt_as_btc: Some(SignedAmount::MAX), + + vb_floor: FeeRate::BROADCAST_MIN, + vb_ceil: FeeRate::BROADCAST_MIN, + kwu: FeeRate::BROADCAST_MIN, + + opt_vb_floor: Some(FeeRate::BROADCAST_MIN), + opt_vb_ceil: Some(FeeRate::BROADCAST_MIN), + opt_kwu: Some(FeeRate::BROADCAST_MIN), + + a: BlockHeight::MAX, + b: BlockInterval::MAX, + c: absolute::Height::MAX, + d: absolute::Time::MAX, + e: relative::Height::MAX, + f: relative::Time::MAX, + g: Weight::MAX, + } + } +} + +#[test] +fn serde_regression() { + let t = Serde::new(); + let got = serialize(&t).unwrap(); + let want = include_bytes!("data/serde_bincode"); + assert_eq!(got, want); +}