diff --git a/units/Cargo.toml b/units/Cargo.toml index 5134003cd..1bad34a7c 100644 --- a/units/Cargo.toml +++ b/units/Cargo.toml @@ -20,12 +20,13 @@ alloc = ["internals/alloc","serde?/alloc"] [dependencies] internals = { package = "bitcoin-internals", path = "../internals" } -serde = { version = "1.0.103", default-features = false, features = ["derive"], optional = true } +serde = { version = "1.0.103", default-features = false, optional = true } arbitrary = { version = "1.4", optional = true } [dev-dependencies] internals = { package = "bitcoin-internals", path = "../internals", features = ["test-serde"] } bincode = "1.3.1" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } serde_test = "1.0" serde_json = "1.0" diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs index 1eebfe6f4..9c1d616f1 100644 --- a/units/src/amount/tests.rs +++ b/units/src/amount/tests.rs @@ -10,9 +10,6 @@ use core::num::{NonZeroI64, NonZeroU64}; #[cfg(feature = "std")] use std::panic; -#[cfg(feature = "serde")] -use ::serde::{Deserialize, Serialize}; - use super::*; #[cfg(feature = "alloc")] use crate::{FeeRate, Weight}; @@ -827,209 +824,6 @@ fn to_string_with_denomination_from_str_roundtrip() { ); } -#[cfg(feature = "serde")] -#[test] -fn serde_as_sat() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct T { - #[serde(with = "crate::amount::serde::as_sat")] - pub amt: Amount, - #[serde(with = "crate::amount::serde::as_sat")] - pub samt: SignedAmount, - } - - serde_test::assert_tokens( - &T { amt: sat(123_456_789), samt: ssat(-123_456_789) }, - &[ - serde_test::Token::Struct { name: "T", len: 2 }, - serde_test::Token::Str("amt"), - serde_test::Token::U64(123_456_789), - serde_test::Token::Str("samt"), - serde_test::Token::I64(-123_456_789), - serde_test::Token::StructEnd, - ], - ); -} - -#[cfg(feature = "serde")] -#[cfg(feature = "alloc")] -#[test] -#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. -fn serde_as_btc() { - use serde_json; - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct T { - #[serde(with = "crate::amount::serde::as_btc")] - pub amt: Amount, - #[serde(with = "crate::amount::serde::as_btc")] - pub samt: SignedAmount, - } - - let orig = T { amt: sat(20_000_000__000_000_01), samt: ssat(-20_000_000__000_000_01) }; - - let json = "{\"amt\": 20000000.00000001, \ - \"samt\": -20000000.00000001}"; - let t: T = serde_json::from_str(json).unwrap(); - assert_eq!(t, orig); - - let value: serde_json::Value = serde_json::from_str(json).unwrap(); - assert_eq!(t, serde_json::from_value(value).unwrap()); - - // errors - let t: Result = - serde_json::from_str("{\"amt\": 1000000.000000001, \"samt\": 1}"); - assert!(t.unwrap_err().to_string().contains( - &ParseAmountError(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 16 })) - .to_string() - )); - let t: Result = serde_json::from_str("{\"amt\": -1, \"samt\": 1}"); - assert!(t.unwrap_err().to_string().contains(&OutOfRangeError::negative().to_string())); -} - -#[cfg(feature = "serde")] -#[cfg(feature = "alloc")] -#[test] -fn serde_as_str() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct T { - #[serde(with = "crate::amount::serde::as_str")] - pub amt: Amount, - #[serde(with = "crate::amount::serde::as_str")] - pub samt: SignedAmount, - } - - serde_test::assert_tokens( - &T { amt: sat(123_456_789), samt: ssat(-123_456_789) }, - &[ - serde_test::Token::Struct { name: "T", len: 2 }, - serde_test::Token::String("amt"), - serde_test::Token::String("1.23456789"), - serde_test::Token::String("samt"), - serde_test::Token::String("-1.23456789"), - serde_test::Token::StructEnd, - ], - ); -} - -#[cfg(feature = "serde")] -#[cfg(feature = "alloc")] -#[test] -#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. -fn serde_as_btc_opt() { - use serde_json; - - #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] - struct T { - #[serde(default, with = "crate::amount::serde::as_btc::opt")] - pub amt: Option, - #[serde(default, with = "crate::amount::serde::as_btc::opt")] - pub samt: Option, - } - - let with = T { amt: Some(sat(2_500_000_00)), samt: Some(ssat(-2_500_000_00)) }; - let without = T { amt: None, samt: None }; - - // Test Roundtripping - for s in [&with, &without] { - let v = serde_json::to_string(s).unwrap(); - let w: T = serde_json::from_str(&v).unwrap(); - assert_eq!(w, *s); - } - - let t: T = serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap(); - assert_eq!(t, with); - - let t: T = serde_json::from_str("{}").unwrap(); - assert_eq!(t, without); - - let value_with: serde_json::Value = - serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap(); - assert_eq!(with, serde_json::from_value(value_with).unwrap()); - - let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); - assert_eq!(without, serde_json::from_value(value_without).unwrap()); -} - -#[cfg(feature = "serde")] -#[cfg(feature = "alloc")] -#[test] -#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. -fn serde_as_sat_opt() { - use serde_json; - - #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] - struct T { - #[serde(default, with = "crate::amount::serde::as_sat::opt")] - pub amt: Option, - #[serde(default, with = "crate::amount::serde::as_sat::opt")] - pub samt: Option, - } - - let with = T { amt: Some(sat(2_500_000_00)), samt: Some(ssat(-2_500_000_00)) }; - let without = T { amt: None, samt: None }; - - // Test Roundtripping - for s in [&with, &without] { - let v = serde_json::to_string(s).unwrap(); - let w: T = serde_json::from_str(&v).unwrap(); - assert_eq!(w, *s); - } - - let t: T = serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap(); - assert_eq!(t, with); - - let t: T = serde_json::from_str("{}").unwrap(); - assert_eq!(t, without); - - let value_with: serde_json::Value = - serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap(); - assert_eq!(with, serde_json::from_value(value_with).unwrap()); - - let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); - assert_eq!(without, serde_json::from_value(value_without).unwrap()); -} - -#[cfg(feature = "serde")] -#[cfg(feature = "alloc")] -#[test] -#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. -fn serde_as_str_opt() { - use serde_json; - - #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] - struct T { - #[serde(default, with = "crate::amount::serde::as_str::opt")] - pub amt: Option, - #[serde(default, with = "crate::amount::serde::as_str::opt")] - pub samt: Option, - } - - let with = T { amt: Some(sat(123_456_789)), samt: Some(ssat(-123_456_789)) }; - let without = T { amt: None, samt: None }; - - // Test Roundtripping - for s in [&with, &without] { - let v = serde_json::to_string(s).unwrap(); - let w: T = serde_json::from_str(&v).unwrap(); - assert_eq!(w, *s); - } - - let t: T = - serde_json::from_str("{\"amt\": \"1.23456789\", \"samt\": \"-1.23456789\"}").unwrap(); - assert_eq!(t, with); - - let t: T = serde_json::from_str("{}").unwrap(); - assert_eq!(t, without); - - let value_with: serde_json::Value = - serde_json::from_str("{\"amt\": \"1.23456789\", \"samt\": \"-1.23456789\"}").unwrap(); - assert_eq!(with, serde_json::from_value(value_with).unwrap()); - - let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); - assert_eq!(without, serde_json::from_value(value_without).unwrap()); -} - #[test] fn sum_amounts() { assert_eq!([].iter().sum::>(), Amount::ZERO.into()); diff --git a/units/tests/serde.rs b/units/tests/serde.rs index 7a38f046e..d312a82fb 100644 --- a/units/tests/serde.rs +++ b/units/tests/serde.rs @@ -88,3 +88,202 @@ fn serde_regression() { let want = include_bytes!("data/serde_bincode"); assert_eq!(got, want); } + +#[track_caller] +fn sat(sat: u64) -> Amount { Amount::from_sat(sat).unwrap() } + +#[track_caller] +fn ssat(ssat: i64) -> SignedAmount { SignedAmount::from_sat(ssat).unwrap() } + +#[cfg(feature = "serde")] +#[test] +fn serde_as_sat() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct T { + #[serde(with = "crate::amount::serde::as_sat")] + pub amt: Amount, + #[serde(with = "crate::amount::serde::as_sat")] + pub samt: SignedAmount, + } + + serde_test::assert_tokens( + &T { amt: sat(123_456_789), samt: ssat(-123_456_789) }, + &[ + serde_test::Token::Struct { name: "T", len: 2 }, + serde_test::Token::Str("amt"), + serde_test::Token::U64(123_456_789), + serde_test::Token::Str("samt"), + serde_test::Token::I64(-123_456_789), + serde_test::Token::StructEnd, + ], + ); +} + +#[cfg(feature = "serde")] +#[cfg(feature = "alloc")] +#[test] +#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. +fn serde_as_btc() { + use serde_json; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct T { + #[serde(with = "crate::amount::serde::as_btc")] + pub amt: Amount, + #[serde(with = "crate::amount::serde::as_btc")] + pub samt: SignedAmount, + } + + let orig = T { amt: sat(20_000_000__000_000_01), samt: ssat(-20_000_000__000_000_01) }; + + let json = "{\"amt\": 20000000.00000001, \ + \"samt\": -20000000.00000001}"; + let t: T = serde_json::from_str(json).unwrap(); + assert_eq!(t, orig); + + let value: serde_json::Value = serde_json::from_str(json).unwrap(); + assert_eq!(t, serde_json::from_value(value).unwrap()); +} + +#[cfg(feature = "serde")] +#[cfg(feature = "alloc")] +#[test] +fn serde_as_str() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct T { + #[serde(with = "crate::amount::serde::as_str")] + pub amt: Amount, + #[serde(with = "crate::amount::serde::as_str")] + pub samt: SignedAmount, + } + + serde_test::assert_tokens( + &T { amt: sat(123_456_789), samt: ssat(-123_456_789) }, + &[ + serde_test::Token::Struct { name: "T", len: 2 }, + serde_test::Token::String("amt"), + serde_test::Token::String("1.23456789"), + serde_test::Token::String("samt"), + serde_test::Token::String("-1.23456789"), + serde_test::Token::StructEnd, + ], + ); +} + +#[cfg(feature = "serde")] +#[cfg(feature = "alloc")] +#[test] +#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. +fn serde_as_btc_opt() { + use serde_json; + + #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] + struct T { + #[serde(default, with = "crate::amount::serde::as_btc::opt")] + pub amt: Option, + #[serde(default, with = "crate::amount::serde::as_btc::opt")] + pub samt: Option, + } + + let with = T { amt: Some(sat(2_500_000_00)), samt: Some(ssat(-2_500_000_00)) }; + let without = T { amt: None, samt: None }; + + // Test Roundtripping + for s in [&with, &without] { + let v = serde_json::to_string(s).unwrap(); + let w: T = serde_json::from_str(&v).unwrap(); + assert_eq!(w, *s); + } + + let t: T = serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap(); + assert_eq!(t, with); + + let t: T = serde_json::from_str("{}").unwrap(); + assert_eq!(t, without); + + let value_with: serde_json::Value = + serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap(); + assert_eq!(with, serde_json::from_value(value_with).unwrap()); + + let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); + assert_eq!(without, serde_json::from_value(value_without).unwrap()); +} + +#[cfg(feature = "serde")] +#[cfg(feature = "alloc")] +#[test] +#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. +fn serde_as_sat_opt() { + use serde_json; + + #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] + struct T { + #[serde(default, with = "crate::amount::serde::as_sat::opt")] + pub amt: Option, + #[serde(default, with = "crate::amount::serde::as_sat::opt")] + pub samt: Option, + } + + let with = T { amt: Some(sat(2_500_000_00)), samt: Some(ssat(-2_500_000_00)) }; + let without = T { amt: None, samt: None }; + + // Test Roundtripping + for s in [&with, &without] { + let v = serde_json::to_string(s).unwrap(); + let w: T = serde_json::from_str(&v).unwrap(); + assert_eq!(w, *s); + } + + let t: T = serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap(); + assert_eq!(t, with); + + let t: T = serde_json::from_str("{}").unwrap(); + assert_eq!(t, without); + + let value_with: serde_json::Value = + serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap(); + assert_eq!(with, serde_json::from_value(value_with).unwrap()); + + let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); + assert_eq!(without, serde_json::from_value(value_without).unwrap()); +} + +#[cfg(feature = "serde")] +#[cfg(feature = "alloc")] +#[test] +#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin. +fn serde_as_str_opt() { + use serde_json; + + #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] + struct T { + #[serde(default, with = "crate::amount::serde::as_str::opt")] + pub amt: Option, + #[serde(default, with = "crate::amount::serde::as_str::opt")] + pub samt: Option, + } + + let with = T { amt: Some(sat(123_456_789)), samt: Some(ssat(-123_456_789)) }; + let without = T { amt: None, samt: None }; + + // Test Roundtripping + for s in [&with, &without] { + let v = serde_json::to_string(s).unwrap(); + let w: T = serde_json::from_str(&v).unwrap(); + assert_eq!(w, *s); + } + + let t: T = + serde_json::from_str("{\"amt\": \"1.23456789\", \"samt\": \"-1.23456789\"}").unwrap(); + assert_eq!(t, with); + + let t: T = serde_json::from_str("{}").unwrap(); + assert_eq!(t, without); + + let value_with: serde_json::Value = + serde_json::from_str("{\"amt\": \"1.23456789\", \"samt\": \"-1.23456789\"}").unwrap(); + assert_eq!(with, serde_json::from_value(value_with).unwrap()); + + let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); + assert_eq!(without, serde_json::from_value(value_without).unwrap()); +}