Implement custom serde modules for FeeRate
The `FeeRate` type wraps a `u64` but the inner value implicitly contains information about the unit. As such when serializing and deserializing the unit information is not explicit and if users try to deserialize with a different unit their code will be silently buggy. As we do for Amount; add custom serde modules so that users can serialize in an explicit unit. Furthermore remove the derived impls forcing users to make the decision. This is as we do for `Amount`. With this applied one can write ```rust #[derive(Serialize, Deserialize)] pub struct Foo { #[serde(with = "bitcoin_units::fee_rate::serde::as_sat_per_kwu")] pub fee_rate: FeeRate, } ```
This commit is contained in:
parent
d94e5f03e6
commit
dedae8acf2
|
@ -2,12 +2,13 @@
|
||||||
|
|
||||||
//! Implements `FeeRate` and assoctiated features.
|
//! Implements `FeeRate` and assoctiated features.
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod serde;
|
||||||
|
|
||||||
use core::{fmt, ops};
|
use core::{fmt, ops};
|
||||||
|
|
||||||
#[cfg(feature = "arbitrary")]
|
#[cfg(feature = "arbitrary")]
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::amount::Amount;
|
use crate::amount::Amount;
|
||||||
use crate::weight::Weight;
|
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
|
/// 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.
|
/// up the types as well as basic formatting features.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[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);
|
pub struct FeeRate(u64);
|
||||||
|
|
||||||
impl FeeRate {
|
impl FeeRate {
|
||||||
|
|
|
@ -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<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
u64::serialize(&f.to_sat_per_kwu(), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
||||||
|
Ok(FeeRate::from_sat_per_kwu(u64::deserialize(d)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod opt {
|
||||||
|
//! Serialize and deserialize [`Option<FeeRate>`] 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<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
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<Option<FeeRate>, D::Error> {
|
||||||
|
struct VisitOpt;
|
||||||
|
|
||||||
|
impl<'de> de::Visitor<'de> for VisitOpt {
|
||||||
|
type Value = Option<FeeRate>;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "An Option<FeeRate>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
|
||||||
|
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<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
u64::serialize(&f.to_sat_per_vb_floor(), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors on overflow.
|
||||||
|
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
||||||
|
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<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::opt")]`.
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
use crate::fee_rate::serde::OverflowError;
|
||||||
|
use crate::fee_rate::FeeRate;
|
||||||
|
|
||||||
|
pub fn serialize<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
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<Option<FeeRate>, D::Error> {
|
||||||
|
struct VisitOpt;
|
||||||
|
|
||||||
|
impl<'de> de::Visitor<'de> for VisitOpt {
|
||||||
|
type Value = Option<FeeRate>;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "An Option<FeeRate>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
|
||||||
|
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<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
u64::serialize(&f.to_sat_per_vb_ceil(), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors on overflow.
|
||||||
|
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
||||||
|
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<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_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<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
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<Option<FeeRate>, D::Error> {
|
||||||
|
struct VisitOpt;
|
||||||
|
|
||||||
|
impl<'de> de::Visitor<'de> for VisitOpt {
|
||||||
|
type Value = Option<FeeRate>;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "An Option<FeeRate>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
|
||||||
|
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 {}
|
Loading…
Reference in New Issue