diff --git a/units/src/fee_rate/mod.rs b/units/src/fee_rate/mod.rs index 2483bd091..b12c08f45 100644 --- a/units/src/fee_rate/mod.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 {}