#![allow(missing_docs)]
#![allow(clippy::missing_errors_doc)]
#[cfg(feature = "alloc")]
use core::fmt;
#[cfg(feature = "alloc")]
use super::ParseAmountError;
use crate::{Amount, SignedAmount};
use sealed::Token;
#[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(feature = "alloc")]
#[cfg(not(feature = "std"))]
impl fmt::Display for DisplayFullError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
pub trait AmountOps: TryFrom<SignedAmount> + Into<SignedAmount> + Copy + sealed::AmountOps {}
impl AmountOps for Amount {}
impl AmountOps for SignedAmount {}
#[allow(private_interfaces)]
mod sealed {
use crate::{Amount, SignedAmount};
pub trait AmountOps {
#[doc(hidden)]
fn is_signed(token: Token) -> bool;
}
impl AmountOps for Amount {
fn is_signed(_: Token) -> bool {
false
}
}
impl AmountOps for SignedAmount {
fn is_signed(_: Token) -> bool {
true
}
}
#[doc(hidden)]
pub(super) struct Token;
}
pub mod as_sat {
use core::fmt;
use core::marker::PhantomData;
use serde::{Deserializer, Serialize, Serializer};
use super::AmountOps;
use crate::SignedAmount;
#[inline]
pub fn serialize<A, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
let sat = (*a).into().to_sat();
if A::is_signed(super::Token) {
i64::serialize(&sat, s)
} else {
u64::serialize(&(sat as u64), s)
}
}
#[inline]
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<A, D::Error>
where
A: AmountOps,
{
fn expecting<T: AmountOps>() -> &'static str {
if T::is_signed(super::Token) {
"an integer between -2100000000000000 and 2100000000000000 inclusive"
} else {
"an integer between 0 and 2100000000000000 inclusive"
}
}
struct Visitor<T>(PhantomData<fn() -> T>);
impl<'de, T> serde::de::Visitor<'de> for Visitor<T>
where
T: AmountOps,
{
type Value = T;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(expecting::<T>())
}
fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Self::Value, E> {
fn range_error<T, E1, E2: serde::de::Error>(value: i64) -> impl FnOnce(E1) -> E2
where
T: AmountOps,
{
move |_| {
let unexpected = serde::de::Unexpected::Signed(value);
E2::invalid_value(unexpected, &expecting::<T>())
}
}
SignedAmount::from_sat(value)
.map_err(range_error::<T, _, _>(value))?
.try_into()
.map_err(range_error::<T, _, _>(value))
}
fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
fn range_error<T, E1, E2: serde::de::Error>(value: u64) -> impl FnOnce(E1) -> E2
where
T: AmountOps,
{
move |_| {
let unexpected = serde::de::Unexpected::Unsigned(value);
E2::invalid_value(unexpected, &expecting::<T>())
}
}
let signed = i64::try_from(value).map_err(range_error::<T, _, _>(value))?;
SignedAmount::from_sat(signed)
.map_err(range_error::<T, _, _>(value))?
.try_into()
.map_err(range_error::<T, _, _>(value))
}
}
if A::is_signed(super::Token) {
d.deserialize_i64(Visitor(PhantomData))
} else {
d.deserialize_u64(Visitor(PhantomData))
}
}
pub mod opt {
use core::fmt;
use core::marker::PhantomData;
use serde::{de, Deserializer, Serialize, Serializer};
use crate::amount::serde::AmountOps;
use crate::SignedAmount;
#[inline]
#[allow(clippy::ref_option)] pub fn serialize<A, S: Serializer>(a: &Option<A>, s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
let sat = a.map(Into::into).map(SignedAmount::to_sat);
if A::is_signed(super::super::Token) {
sat.serialize(s)
} else {
sat.map(|sat| sat as u64).serialize(s)
}
}
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<Option<A>, D::Error>
where
A: AmountOps,
{
fn expecting<T: AmountOps>() -> &'static str {
if T::is_signed(super::super::Token) {
"an optional integer between -2100000000000000 and 2100000000000000 inclusive"
} else {
"an optional integer between 0 and 2100000000000000 inclusive"
}
}
struct VisitOptAmt<X>(PhantomData<X>);
impl<'de, X> de::Visitor<'de> for VisitOptAmt<X>
where
X: AmountOps,
{
type Value = Option<X>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(expecting::<X>())
}
#[inline]
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
#[inline]
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(super::deserialize(d)?))
}
}
d.deserialize_option(VisitOptAmt::<A>(PhantomData))
}
}
#[cfg(feature = "alloc")]
pub mod vec {
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use serde::de::{self, SeqAccess};
use serde::{Deserialize, Deserializer, Serializer};
use crate::amount::serde::AmountOps;
use crate::SignedAmount;
#[inline]
pub fn serialize<A, S: Serializer>(a: &[A], s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
let sats = a.iter().map(|&amount| amount.into()).map(SignedAmount::to_sat);
if A::is_signed(super::super::Token) {
s.collect_seq(sats)
} else {
s.collect_seq(sats.map(|sat| sat as u64))
}
}
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<Vec<A>, D::Error>
where
A: AmountOps,
{
fn expecting<T: AmountOps>() -> &'static str {
if T::is_signed(super::super::Token) {
"an sequence of integers between -2100000000000000 and 2100000000000000 inclusive"
} else {
"an sequence of integers between 0 and 2100000000000000 inclusive"
}
}
struct VisitVec<X>(PhantomData<X>);
impl<'de, X> de::Visitor<'de> for VisitVec<X>
where
X: AmountOps,
{
type Value = Vec<X>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(expecting::<X>())
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
#[derive(Deserialize)]
#[serde(transparent)]
struct Wrapper<T: AmountOps>(#[serde(with = "super")] T);
let mut out = Vec::with_capacity(internals::serde::cautious_size_hint::<X>(
seq.size_hint(),
));
while let Some(wrapped) = seq.next_element::<Wrapper<X>>()? {
out.push(wrapped.0);
}
Ok(out)
}
}
d.deserialize_seq(VisitVec::<A>(PhantomData))
}
}
}
#[cfg(feature = "alloc")]
pub mod as_btc {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{AmountOps, DisplayFullError};
use crate::amount::{Denomination, SignedAmount};
#[inline]
pub fn serialize<A, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
let amount: SignedAmount = (*a).into();
f64::serialize(&amount.to_float_in(Denomination::Bitcoin), s)
}
#[inline]
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<A, D::Error>
where
A: AmountOps,
A::Error: core::fmt::Display,
{
let btc = f64::deserialize(d)?;
let amount = SignedAmount::from_btc(btc)
.map_err(DisplayFullError)
.map_err(serde::de::Error::custom)?;
A::try_from(amount).map_err(serde::de::Error::custom)
}
pub mod opt {
use core::fmt;
use core::marker::PhantomData;
use serde::{de, Deserializer, Serializer};
use crate::amount::serde::AmountOps;
use crate::amount::Denomination;
#[inline]
#[allow(clippy::ref_option)] pub fn serialize<A, S: Serializer>(a: &Option<A>, s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
match a.map(Into::into).map(|amt| amt.to_float_in(Denomination::Bitcoin)) {
Some(a) => s.serialize_some(&a),
None => s.serialize_none(),
}
}
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<Option<A>, D::Error>
where
A: AmountOps,
A::Error: core::fmt::Display,
{
struct VisitOptAmt<X>(PhantomData<X>);
impl<'de, X> de::Visitor<'de> for VisitOptAmt<X>
where
X: AmountOps,
X::Error: core::fmt::Display,
{
type Value = Option<X>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an Option<f64>")
}
#[inline]
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
#[inline]
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(super::deserialize(d)?))
}
}
d.deserialize_option(VisitOptAmt::<A>(PhantomData))
}
}
pub mod vec {
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use serde::de::{self, SeqAccess};
use serde::{Deserialize, Deserializer, Serializer};
use crate::amount::serde::AmountOps;
use crate::amount::{Denomination, SignedAmount};
#[inline]
pub fn serialize<A, S: Serializer>(a: &[A], s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
s.collect_seq(a.iter().map(|amount| {
let signed_amount: SignedAmount = (*amount).into();
signed_amount.to_float_in(Denomination::Bitcoin)
}))
}
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<Vec<A>, D::Error>
where
A: AmountOps,
A::Error: core::fmt::Display,
{
struct VisitVec<X>(PhantomData<X>);
impl<'de, X> de::Visitor<'de> for VisitVec<X>
where
X: AmountOps,
X::Error: core::fmt::Display,
{
type Value = Vec<X>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a sequence of f64")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
#[derive(Deserialize)]
#[serde(transparent)]
struct Wrapper(#[serde(with = "super")] SignedAmount);
let mut out = Vec::with_capacity(internals::serde::cautious_size_hint::<X>(
seq.size_hint(),
));
while let Some(wrapped) = seq.next_element::<Wrapper>()? {
out.push(X::try_from(wrapped.0).map_err(de::Error::custom)?);
}
Ok(out)
}
}
d.deserialize_seq(VisitVec::<A>(PhantomData))
}
}
}
#[cfg(feature = "alloc")]
pub mod as_str {
use alloc::string::String;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{AmountOps, DisplayFullError};
use crate::amount::{Denomination, SignedAmount};
#[inline]
pub fn serialize<A, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
let amount: SignedAmount = (*a).into();
str::serialize(&amount.to_string_in(Denomination::Bitcoin), s)
}
#[inline]
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<A, D::Error>
where
A: AmountOps,
A::Error: core::fmt::Display,
{
let btc = String::deserialize(d)?;
let amount = SignedAmount::from_str_in(&btc, Denomination::Bitcoin)
.map_err(DisplayFullError)
.map_err(serde::de::Error::custom)?;
A::try_from(amount).map_err(serde::de::Error::custom)
}
pub mod opt {
use core::fmt;
use core::marker::PhantomData;
use serde::{de, Deserializer, Serializer};
use crate::amount::serde::AmountOps;
use crate::amount::Denomination;
#[inline]
#[allow(clippy::ref_option)] pub fn serialize<A, S: Serializer>(a: &Option<A>, s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
match a.map(Into::into).map(|amt| amt.to_string_in(Denomination::Bitcoin)) {
Some(a) => s.serialize_some(&a),
None => s.serialize_none(),
}
}
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<Option<A>, D::Error>
where
A: AmountOps,
A::Error: core::fmt::Display,
{
struct VisitOptAmt<X>(PhantomData<X>);
impl<'de, X> de::Visitor<'de> for VisitOptAmt<X>
where
X: AmountOps,
X::Error: core::fmt::Display,
{
type Value = Option<X>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an Option<String>")
}
#[inline]
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
#[inline]
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(super::deserialize(d)?))
}
}
d.deserialize_option(VisitOptAmt::<A>(PhantomData))
}
}
pub mod vec {
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use serde::de::{self, SeqAccess};
use serde::{Deserialize, Deserializer, Serializer};
use crate::amount::serde::AmountOps;
use crate::amount::{Denomination, SignedAmount};
#[inline]
pub fn serialize<A, S: Serializer>(a: &[A], s: S) -> Result<S::Ok, S::Error>
where
A: AmountOps,
{
s.collect_seq(a.iter().map(|amount| {
let signed_amount: SignedAmount = (*amount).into();
signed_amount.to_string_in(Denomination::Bitcoin)
}))
}
pub fn deserialize<'d, A, D: Deserializer<'d>>(d: D) -> Result<Vec<A>, D::Error>
where
A: AmountOps,
A::Error: core::fmt::Display,
{
struct VisitVec<X>(PhantomData<X>);
impl<'de, X> de::Visitor<'de> for VisitVec<X>
where
X: AmountOps,
X::Error: core::fmt::Display,
{
type Value = Vec<X>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a sequence of String")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
#[derive(Deserialize)]
#[serde(transparent)]
struct Wrapper(#[serde(with = "super")] SignedAmount);
let mut out = Vec::with_capacity(internals::serde::cautious_size_hint::<X>(
seq.size_hint(),
));
while let Some(wrapped) = seq.next_element::<Wrapper>()? {
out.push(X::try_from(wrapped.0).map_err(de::Error::custom)?);
}
Ok(out)
}
}
d.deserialize_seq(VisitVec::<A>(PhantomData))
}
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use crate::amount::{self, Amount, SignedAmount};
#[test]
fn can_serde_as_sat() {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct HasAmount {
#[serde(with = "amount::serde::as_sat")]
pub amount: Amount,
#[serde(with = "amount::serde::as_sat")]
pub signed_amount: SignedAmount,
}
let orig = HasAmount { amount: Amount::ONE_BTC, signed_amount: SignedAmount::ONE_BTC };
let json = serde_json::to_string(&orig).expect("failed to ser");
let want = "{\"amount\":100000000,\"signed_amount\":100000000}";
assert_eq!(json, want);
let roundtrip: HasAmount = serde_json::from_str(&json).expect("failed to deser");
assert_eq!(roundtrip, orig);
}
#[test]
#[cfg(feature = "alloc")]
fn can_serde_as_btc() {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct HasAmount {
#[serde(with = "amount::serde::as_btc")]
pub amount: Amount,
#[serde(with = "amount::serde::as_btc")]
pub signed_amount: SignedAmount,
}
let orig = HasAmount { amount: Amount::ONE_BTC, signed_amount: SignedAmount::ONE_BTC };
let json = serde_json::to_string(&orig).expect("failed to ser");
let want = "{\"amount\":1.0,\"signed_amount\":1.0}";
assert_eq!(json, want);
let roundtrip: HasAmount = serde_json::from_str(&json).expect("failed to deser");
assert_eq!(roundtrip, orig);
}
#[test]
#[cfg(feature = "alloc")]
fn can_serde_as_str() {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct HasAmount {
#[serde(with = "amount::serde::as_str")]
pub amount: Amount,
#[serde(with = "amount::serde::as_str")]
pub signed_amount: SignedAmount,
}
let orig = HasAmount { amount: Amount::ONE_BTC, signed_amount: SignedAmount::ONE_BTC };
let json = serde_json::to_string(&orig).expect("failed to ser");
let want = "{\"amount\":\"1\",\"signed_amount\":\"1\"}";
assert_eq!(json, want);
let roundtrip: HasAmount = serde_json::from_str(&json).expect("failed to deser");
assert_eq!(roundtrip, orig);
}
}