units: Remove serde derive feature

Currently we only need the `derive` feature of `serde` in test code.

Observe:

- We do not need the error testing logic because `ParseAmountError` is
already exhaustively tested.
- The rest of the `serde` test logic in `amount` can be done using the
public API so it can be moved to the integration test directory.

Move the unit test code to `tests/` excluding the error testing logic.
Remove the `derive` feature from the `serde` dependency. Add a
`dev-dependency` on `serde` that enables the `derive` feature.
This commit is contained in:
Tobin C. Harding 2025-05-15 08:48:37 +10:00
parent 1031851da4
commit d8377d90dd
No known key found for this signature in database
GPG Key ID: 0AEF0A899E41F7DD
3 changed files with 201 additions and 207 deletions

View File

@ -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"

View File

@ -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<T, serde_json::Error> =
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<T, serde_json::Error> = 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<Amount>,
#[serde(default, with = "crate::amount::serde::as_btc::opt")]
pub samt: Option<SignedAmount>,
}
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<Amount>,
#[serde(default, with = "crate::amount::serde::as_sat::opt")]
pub samt: Option<SignedAmount>,
}
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<Amount>,
#[serde(default, with = "crate::amount::serde::as_str::opt")]
pub samt: Option<SignedAmount>,
}
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::<NumOpResult<Amount>>(), Amount::ZERO.into());

View File

@ -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<Amount>,
#[serde(default, with = "crate::amount::serde::as_btc::opt")]
pub samt: Option<SignedAmount>,
}
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<Amount>,
#[serde(default, with = "crate::amount::serde::as_sat::opt")]
pub samt: Option<SignedAmount>,
}
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<Amount>,
#[serde(default, with = "crate::amount::serde::as_str::opt")]
pub samt: Option<SignedAmount>,
}
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());
}