Skip to content

Commit 795ac3e

Browse files
committed
Merge rust-bitcoin#4933: Manually implement serde traits for OutPoint
42d3039 Manually implement serde traits for OutPoint (Tobin C. Harding) 0b0980b primitives: Add serde unit tests for OutPoint (Tobin C. Harding) Pull request description: Death to all macros. This was pulled out of rust-bitcoin#4902, review suggestions over there are relevant. Also I started rust-bitcoin#4932 after working on this. ACKs for top commit: apoelstra: ACK 42d3039; successfully ran local tests Tree-SHA512: 2625796ac05426a464d45a01c52fec6ad7c5911120c4ebd97883c89779f981ba42b7394cad6786857ddf49f951610814b7c477fed27a664d184dc97c1177b718
2 parents 3a364ad + 42d3039 commit 795ac3e

File tree

1 file changed

+179
-2
lines changed

1 file changed

+179
-2
lines changed

primitives/src/transaction.rs

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use hashes::sha256d;
2424
use internals::compact_size;
2525
#[cfg(feature = "hex")]
2626
use internals::write_err;
27+
#[cfg(feature = "serde")]
28+
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
2729
#[cfg(feature = "hex")]
2830
use units::parse_int;
2931

@@ -357,8 +359,6 @@ pub struct OutPoint {
357359
/// The index of the referenced output in its transaction's vout.
358360
pub vout: u32,
359361
}
360-
#[cfg(feature = "serde")]
361-
internals::serde_struct_human_string_impl!(OutPoint, "an OutPoint", txid, vout);
362362

363363
impl OutPoint {
364364
/// The number of bytes that an outpoint contributes to the size of a transaction.
@@ -419,6 +419,120 @@ fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> {
419419
parse_int::int_from_str(s).map_err(ParseOutPointError::Vout)
420420
}
421421

422+
#[cfg(feature = "serde")]
423+
impl Serialize for OutPoint {
424+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
425+
where
426+
S: Serializer,
427+
{
428+
if serializer.is_human_readable() {
429+
serializer.collect_str(&self)
430+
} else {
431+
use crate::serde::ser::SerializeStruct as _;
432+
433+
let mut state = serializer.serialize_struct("OutPoint", 2)?;
434+
// serializing as an array was found in the past to break for some serializers so we use
435+
// a slice instead. This causes 8 bytes to be prepended for the length (even though this
436+
// is a bit silly because know the length).
437+
state.serialize_field("txid", self.txid.as_byte_array().as_slice())?;
438+
state.serialize_field("vout", &self.vout.to_le_bytes())?;
439+
state.end()
440+
}
441+
}
442+
}
443+
444+
#[cfg(feature = "serde")]
445+
impl<'de> Deserialize<'de> for OutPoint {
446+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
447+
where
448+
D: Deserializer<'de>,
449+
{
450+
if deserializer.is_human_readable() {
451+
struct StringVisitor;
452+
453+
impl<'de> de::Visitor<'de> for StringVisitor {
454+
type Value = OutPoint;
455+
456+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
457+
formatter.write_str("a string in format 'txid:vout'")
458+
}
459+
460+
fn visit_str<E>(self, value: &str) -> Result<OutPoint, E>
461+
where
462+
E: de::Error,
463+
{
464+
value.parse::<OutPoint>().map_err(de::Error::custom)
465+
}
466+
}
467+
468+
deserializer.deserialize_str(StringVisitor)
469+
} else {
470+
#[derive(Deserialize)]
471+
#[serde(field_identifier, rename_all = "lowercase")]
472+
enum Field {
473+
Txid,
474+
Vout,
475+
}
476+
477+
struct OutPointVisitor;
478+
479+
impl<'de> de::Visitor<'de> for OutPointVisitor {
480+
type Value = OutPoint;
481+
482+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
483+
formatter.write_str("OutPoint struct with fields")
484+
}
485+
486+
fn visit_seq<V>(self, mut seq: V) -> Result<OutPoint, V::Error>
487+
where
488+
V: de::SeqAccess<'de>,
489+
{
490+
let txid =
491+
seq.next_element()?.ok_or_else(|| de::Error::invalid_length(0, &self))?;
492+
let vout =
493+
seq.next_element()?.ok_or_else(|| de::Error::invalid_length(1, &self))?;
494+
Ok(OutPoint { txid, vout })
495+
}
496+
497+
fn visit_map<V>(self, mut map: V) -> Result<OutPoint, V::Error>
498+
where
499+
V: de::MapAccess<'de>,
500+
{
501+
let mut txid = None;
502+
let mut vout = None;
503+
504+
while let Some(key) = map.next_key()? {
505+
match key {
506+
Field::Txid => {
507+
if txid.is_some() {
508+
return Err(de::Error::duplicate_field("txid"));
509+
}
510+
let bytes: [u8; 32] = map.next_value()?;
511+
txid = Some(Txid::from_byte_array(bytes));
512+
}
513+
Field::Vout => {
514+
if vout.is_some() {
515+
return Err(de::Error::duplicate_field("vout"));
516+
}
517+
let bytes: [u8; 4] = map.next_value()?;
518+
vout = Some(u32::from_le_bytes(bytes));
519+
}
520+
}
521+
}
522+
523+
let txid = txid.ok_or_else(|| de::Error::missing_field("txid"))?;
524+
let vout = vout.ok_or_else(|| de::Error::missing_field("vout"))?;
525+
526+
Ok(OutPoint { txid, vout })
527+
}
528+
}
529+
530+
const FIELDS: &[&str] = &["txid", "vout"];
531+
deserializer.deserialize_struct("OutPoint", FIELDS, OutPointVisitor)
532+
}
533+
}
534+
}
535+
422536
/// An error in parsing an [`OutPoint`].
423537
#[derive(Debug, Clone, PartialEq, Eq)]
424538
#[non_exhaustive]
@@ -770,4 +884,67 @@ mod tests {
770884
let version = Version(123);
771885
assert_eq!(format!("{}", version), "123");
772886
}
887+
888+
// Creates an arbitrary dummy outpoint.
889+
#[cfg(feature = "serde")]
890+
fn tc_out_point() -> OutPoint {
891+
let s = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20:1";
892+
s.parse::<OutPoint>().unwrap()
893+
}
894+
895+
#[test]
896+
#[cfg(feature = "serde")]
897+
fn out_point_serde_deserialize_human_readable() {
898+
// `sered` serialization is the same as `Display` but includes quotes.
899+
let ser = "\"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20:1\"";
900+
let got = serde_json::from_str::<OutPoint>(ser).unwrap();
901+
let want = tc_out_point();
902+
903+
assert_eq!(got, want);
904+
}
905+
906+
#[test]
907+
#[cfg(feature = "serde")]
908+
fn out_point_serde_deserialize_non_human_readable() {
909+
#[rustfmt::skip]
910+
let bytes = [
911+
// Length, pre-pended by the `serde` infrastructure because we use
912+
// slice serialization instead of array even though we know the length.
913+
32, 0, 0, 0, 0, 0, 0, 0,
914+
// The txid bytes
915+
32, 31, 30, 29, 28, 27, 26, 25,
916+
24, 23, 22, 21, 20, 19, 18, 17,
917+
16, 15, 14, 13, 12, 11, 10, 9,
918+
8, 7, 6, 5, 4, 3, 2, 1,
919+
// The vout
920+
1, 0, 0, 0
921+
];
922+
923+
let got = bincode::deserialize::<OutPoint>(&bytes).unwrap();
924+
let want = tc_out_point();
925+
926+
assert_eq!(got, want);
927+
}
928+
929+
#[test]
930+
#[cfg(feature = "serde")]
931+
fn out_point_serde_human_readable_rountrips() {
932+
let out_point = tc_out_point();
933+
934+
let ser = serde_json::to_string(&out_point).unwrap();
935+
let got = serde_json::from_str::<OutPoint>(&ser).unwrap();
936+
937+
assert_eq!(got, out_point);
938+
}
939+
940+
#[test]
941+
#[cfg(feature = "serde")]
942+
fn out_point_serde_non_human_readable_rountrips() {
943+
let out_point = tc_out_point();
944+
945+
let ser = bincode::serialize(&out_point).unwrap();
946+
let got = bincode::deserialize::<OutPoint>(&ser).unwrap();
947+
948+
assert_eq!(got, out_point);
949+
}
773950
}

0 commit comments

Comments
 (0)