1use crate::shim::actors::miner::DeadlineInfo;
125use derive_more::From;
126use fvm_shared4::piece::PaddedPieceSize;
127#[cfg(test)]
128use pretty_assertions::assert_eq;
129use schemars::{JsonSchema, Schema, SchemaGenerator};
130use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
131#[cfg(test)]
132use serde_json::json;
133use std::{fmt::Display, str::FromStr};
134use uuid::Uuid;
135
136pub trait HasLotusJson: Sized {
137 type LotusJson: Serialize + DeserializeOwned;
139 #[cfg(test)]
147 fn snapshots() -> Vec<(serde_json::Value, Self)>;
148 fn into_lotus_json(self) -> Self::LotusJson;
149 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self;
150 fn into_lotus_json_value(self) -> serde_json::Result<serde_json::Value> {
151 serde_json::to_value(self.into_lotus_json())
152 }
153 fn into_lotus_json_string(self) -> serde_json::Result<String> {
154 serde_json::to_string(&self.into_lotus_json())
155 }
156 fn into_lotus_json_string_pretty(self) -> serde_json::Result<String> {
157 serde_json::to_string_pretty(&self.into_lotus_json())
158 }
159}
160
161macro_rules! decl_and_test {
162 ($($mod_name:ident for $domain_ty:ty),* $(,)?) => {
163 $(
164 mod $mod_name;
165 )*
166 #[test]
167 fn all_snapshots() {
168 $(
169 print!("test snapshots for {}...", std::any::type_name::<$domain_ty>());
170 std::io::Write::flush(&mut std::io::stdout()).unwrap();
171 assert_all_snapshots::<$domain_ty>();
173 println!("ok.");
174 )*
175 }
176 #[test]
177 fn all_quickchecks() {
178 $(
179 print!("quickcheck for {}...", std::any::type_name::<$domain_ty>());
180 std::io::Write::flush(&mut std::io::stdout()).unwrap();
181 ::quickcheck::quickcheck(assert_unchanged_via_json::<$domain_ty> as fn(_));
183 println!("ok.");
184 )*
185 }
186 }
187}
188#[cfg(doc)]
189pub(crate) use decl_and_test;
190
191decl_and_test!(
192 actor_state for crate::shim::state_tree::ActorState,
193 address for crate::shim::address::Address,
194 beacon_entry for crate::beacon::BeaconEntry,
195 big_int for num::BigInt,
196 block_header for crate::blocks::CachingBlockHeader,
197 cid for ::cid::Cid,
198 duration for std::time::Duration,
199 percent for crate::shim::percent::Percent,
200 election_proof for crate::blocks::ElectionProof,
201 extended_sector_info for crate::shim::sector::ExtendedSectorInfo,
202 gossip_block for crate::blocks::GossipBlock,
203 key_info for crate::key_management::KeyInfo,
204 message for crate::shim::message::Message,
205 po_st_proof for crate::shim::sector::PoStProof,
206 registered_po_st_proof for crate::shim::sector::RegisteredPoStProof,
207 registered_seal_proof for crate::shim::sector::RegisteredSealProof,
208 sector_info for crate::shim::sector::SectorInfo,
209 sector_size for crate::shim::sector::SectorSize,
210 signature for crate::shim::crypto::Signature,
211 signature_type for crate::shim::crypto::SignatureType,
212 signed_message for crate::message::SignedMessage,
213 ticket for crate::blocks::Ticket,
214 tipset_keys for crate::blocks::TipsetKey,
215 token_amount for crate::shim::econ::TokenAmount,
216 vec_u8 for Vec<u8>,
217 vrf_proof for crate::blocks::VRFProof,
218);
219
220mod actors;
223mod allocation;
224mod arc;
225mod beneficiary_term; mod bit_field; mod bytecode_hash;
228mod entry;
229mod filter_estimate;
230mod hash_map;
231mod ipld; mod miner_info; mod miner_power; mod nonempty; mod opt; mod padded_piece_size;
237mod pending_beneficiary_change; mod power_claim; mod raw_bytes; mod receipt; mod token_state;
242mod tombstone;
243mod transient_data;
244mod vec; mod verifreg_claim;
246
247pub use vec::*;
248
249#[macro_export]
250macro_rules! test_snapshots {
251 ($ty:ty) => {
252 pastey::paste! {
253 #[test]
254 fn [<snapshots_ $ty:snake>]() {
255 use super::*;
256 assert_all_snapshots::<$ty>();
257 }
258 }
259 };
260
261 ($module:path: $ty:ident: $($version:literal),+ $(,)?) => {
262 $(
263 pastey::paste! {
264 #[test]
265 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
266 use super::*;
267 assert_all_snapshots::<$module::[<v $version>]::$ty>();
268 }
269 }
270 )+
271 };
272
273 ($module:path: $nested_path:path: $ty:ident: $($version:literal),+ $(,)?) => {
274 $(
275 pastey::paste! {
276 #[test]
277 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
278 use super::*;
279 assert_all_snapshots::<$module::[<v $version>]::$nested_path::$ty>();
280 }
281 }
282 )+
283 };
284}
285
286#[cfg(any(test, doc))]
287pub fn assert_all_snapshots<T>()
288where
289 T: HasLotusJson,
290 <T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
291{
292 let snapshots = T::snapshots();
293 assert!(!snapshots.is_empty());
294 for (lotus_json, val) in snapshots {
295 assert_one_snapshot(lotus_json, val);
296 }
297}
298
299#[cfg(test)]
300pub fn assert_one_snapshot<T>(lotus_json: serde_json::Value, val: T)
301where
302 T: HasLotusJson,
303 <T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
304{
305 let val_lotus_json = val.into_lotus_json();
307 let serialized = serde_json::to_value(&val_lotus_json).unwrap();
308 assert_eq!(
309 serialized.to_string(),
310 lotus_json.to_string(),
311 "snapshot failed for {}",
312 std::any::type_name::<T>()
313 );
314
315 let deserialized = match serde_json::from_value::<T::LotusJson>(lotus_json.clone()) {
318 Ok(lotus_json) => T::from_lotus_json(lotus_json).into_lotus_json(),
319 Err(e) => panic!(
320 "couldn't deserialize a {} from {}: {e}",
321 std::any::type_name::<T::LotusJson>(),
322 lotus_json
323 ),
324 };
325 assert_eq!(deserialized, val_lotus_json);
326}
327
328#[cfg(any(test, doc))]
329pub fn assert_unchanged_via_json<T>(val: T)
330where
331 T: HasLotusJson + Clone + PartialEq + std::fmt::Debug,
332 T::LotusJson: Serialize + serde::de::DeserializeOwned,
333{
334 let temp = val.clone().into_lotus_json();
338 let temp = serde_json::to_value(temp).unwrap();
340 let temp = serde_json::from_value::<T::LotusJson>(temp).unwrap();
342 let temp = T::from_lotus_json(temp);
344
345 assert_eq!(val, temp);
346}
347
348pub mod stringify {
350 use super::*;
351
352 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
353 where
354 T: Display,
355 S: Serializer,
356 {
357 serializer.collect_str(value)
358 }
359
360 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
361 where
362 T: FromStr,
363 T::Err: Display,
364 D: Deserializer<'de>,
365 {
366 String::deserialize(deserializer)?
367 .parse()
368 .map_err(serde::de::Error::custom)
369 }
370}
371
372pub mod hexify_bytes {
374 use super::*;
375
376 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
377 where
378 T: AsRef<[u8]>,
379 S: Serializer,
380 {
381 serializer.serialize_str(&crate::utils::encoding::hex::encode_prefixed(value))
384 }
385
386 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
387 where
388 T: FromStr,
389 T::Err: Display,
390 D: Deserializer<'de>,
391 {
392 String::deserialize(deserializer)?
393 .parse()
394 .map_err(serde::de::Error::custom)
395 }
396}
397
398pub mod hexify_vec_bytes {
399 use super::*;
400 use std::borrow::Cow;
401
402 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
403 where
404 S: Serializer,
405 {
406 serializer.serialize_str(&crate::utils::encoding::hex::encode_prefixed(value))
407 }
408
409 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
410 where
411 D: Deserializer<'de>,
412 {
413 let s = String::deserialize(deserializer)?;
414 let s = Cow::from(s.strip_prefix("0x").unwrap_or(&s));
415
416 let s = if s.len().is_multiple_of(2) {
419 s
420 } else {
421 let mut s = s.into_owned();
422 s.insert(0, '0');
423 Cow::Owned(s)
424 };
425
426 crate::utils::encoding::hex::decode(s.as_ref()).map_err(serde::de::Error::custom)
427 }
428}
429
430pub mod hexify {
432 use super::*;
433 use num_traits::Num;
434 use serde::{Deserializer, Serializer};
435
436 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
437 where
438 T: Num + std::fmt::LowerHex,
439 S: Serializer,
440 {
441 serializer.serialize_str(format!("{value:#x}").as_str())
442 }
443
444 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
445 where
446 T: Num,
447 <T as Num>::FromStrRadixErr: std::fmt::Display,
448 D: Deserializer<'de>,
449 {
450 let s = String::deserialize(deserializer)?;
451 #[allow(clippy::indexing_slicing)]
452 if s.len() > 2 && &s[..2] == "0x" {
453 T::from_str_radix(&s[2..], 16).map_err(serde::de::Error::custom)
454 } else {
455 Err(serde::de::Error::custom("Invalid hex"))
456 }
457 }
458}
459
460pub mod base64_standard {
462 use super::*;
463
464 use base64::engine::{Engine as _, general_purpose::STANDARD};
465
466 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
467 where
468 S: Serializer,
469 {
470 STANDARD.encode(value).serialize(serializer)
471 }
472
473 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
474 where
475 D: Deserializer<'de>,
476 {
477 STANDARD
478 .decode(String::deserialize(deserializer)?)
479 .map_err(serde::de::Error::custom)
480 }
481}
482
483pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
485where
486 S: Serializer,
487 T: HasLotusJson + Clone,
488{
489 value.clone().into_lotus_json().serialize(serializer)
490}
491
492pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
494where
495 D: Deserializer<'de>,
496 T: HasLotusJson,
497{
498 Ok(T::from_lotus_json(Deserialize::deserialize(deserializer)?))
499}
500
501#[derive(
503 Debug, Deserialize, From, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Clone,
504)]
505#[serde(bound = "T: HasLotusJson + Clone", transparent)]
506pub struct LotusJson<T>(#[serde(with = "self")] pub T);
507
508impl<T> JsonSchema for LotusJson<T>
509where
510 T: HasLotusJson,
511 T::LotusJson: JsonSchema,
512{
513 fn schema_name() -> std::borrow::Cow<'static, str> {
514 T::LotusJson::schema_name()
515 }
516
517 fn schema_id() -> std::borrow::Cow<'static, str> {
518 T::LotusJson::schema_id()
519 }
520
521 fn json_schema(g: &mut SchemaGenerator) -> Schema {
522 T::LotusJson::json_schema(g)
523 }
524}
525
526impl<T> LotusJson<T> {
527 pub fn into_inner(self) -> T {
528 self.0
529 }
530}
531
532macro_rules! lotus_json_with_self {
533 ($($domain_ty:ty),* $(,)?) => {
534 $(
535 impl $crate::lotus_json::HasLotusJson for $domain_ty {
536 type LotusJson = Self;
537 #[cfg(test)]
538 fn snapshots() -> Vec<(serde_json::Value, Self)> {
539 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
540 }
541 fn into_lotus_json(self) -> Self::LotusJson {
542 self
543 }
544 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
545 lotus_json
546 }
547 }
548 )*
549 }
550}
551pub(crate) use lotus_json_with_self;
552
553lotus_json_with_self!(
554 u32,
555 u64,
556 i64,
557 f64,
558 String,
559 chrono::DateTime<chrono::Utc>,
560 serde_json::Value,
561 (),
562 std::path::PathBuf,
563 bool,
564 DeadlineInfo,
565 PaddedPieceSize,
566 Uuid,
567 std::num::NonZeroU32,
568 std::num::NonZeroUsize,
569);
570
571mod fixme {
572 use super::*;
573
574 impl<T: HasLotusJson> HasLotusJson for (T,) {
575 type LotusJson = (T::LotusJson,);
576 #[cfg(test)]
577 fn snapshots() -> Vec<(serde_json::Value, Self)> {
578 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
579 }
580 fn into_lotus_json(self) -> Self::LotusJson {
581 (self.0.into_lotus_json(),)
582 }
583 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
584 (HasLotusJson::from_lotus_json(lotus_json.0),)
585 }
586 }
587
588 impl<A: HasLotusJson, B: HasLotusJson> HasLotusJson for (A, B) {
589 type LotusJson = (A::LotusJson, B::LotusJson);
590 #[cfg(test)]
591 fn snapshots() -> Vec<(serde_json::Value, Self)> {
592 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
593 }
594 fn into_lotus_json(self) -> Self::LotusJson {
595 (self.0.into_lotus_json(), self.1.into_lotus_json())
596 }
597 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
598 (
599 HasLotusJson::from_lotus_json(lotus_json.0),
600 HasLotusJson::from_lotus_json(lotus_json.1),
601 )
602 }
603 }
604
605 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson> HasLotusJson for (A, B, C) {
606 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson);
607 #[cfg(test)]
608 fn snapshots() -> Vec<(serde_json::Value, Self)> {
609 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
610 }
611 fn into_lotus_json(self) -> Self::LotusJson {
612 (
613 self.0.into_lotus_json(),
614 self.1.into_lotus_json(),
615 self.2.into_lotus_json(),
616 )
617 }
618 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
619 (
620 HasLotusJson::from_lotus_json(lotus_json.0),
621 HasLotusJson::from_lotus_json(lotus_json.1),
622 HasLotusJson::from_lotus_json(lotus_json.2),
623 )
624 }
625 }
626
627 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson, D: HasLotusJson> HasLotusJson
628 for (A, B, C, D)
629 {
630 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson, D::LotusJson);
631 #[cfg(test)]
632 fn snapshots() -> Vec<(serde_json::Value, Self)> {
633 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
634 }
635 fn into_lotus_json(self) -> Self::LotusJson {
636 (
637 self.0.into_lotus_json(),
638 self.1.into_lotus_json(),
639 self.2.into_lotus_json(),
640 self.3.into_lotus_json(),
641 )
642 }
643 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
644 (
645 HasLotusJson::from_lotus_json(lotus_json.0),
646 HasLotusJson::from_lotus_json(lotus_json.1),
647 HasLotusJson::from_lotus_json(lotus_json.2),
648 HasLotusJson::from_lotus_json(lotus_json.3),
649 )
650 }
651 }
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657 use ipld_core::serde::SerdeError;
658 use quickcheck_macros::quickcheck;
659 use serde::de::{IntoDeserializer, value::StringDeserializer};
660
661 #[derive(Debug, Deserialize, Serialize, PartialEq)]
662 struct HexifyVecBytesTest {
663 #[serde(with = "hexify_vec_bytes")]
664 value: Vec<u8>,
665 }
666
667 fn matches_legacy_lowerhex<T: AsRef<[u8]> + std::fmt::LowerHex>(value: T) -> bool {
670 #[derive(Serialize)]
671 struct W<T: AsRef<[u8]>>(#[serde(with = "hexify_bytes")] T);
672
673 serde_json::to_string(&W(&value)).unwrap() == format!("\"{value:#x}\"")
674 }
675
676 fn filled<const N: usize>(bytes: Vec<u8>) -> [u8; N] {
677 let mut arr = [0; N];
678 arr.iter_mut().zip(bytes).for_each(|(a, b)| *a = b);
679 arr
680 }
681
682 #[test]
683 fn test_hexify_deserialize() {
684 fn de(input: &str) -> Result<u64, SerdeError> {
685 let deserializer: StringDeserializer<SerdeError> =
686 String::from_str(input).unwrap().into_deserializer();
687 hexify::deserialize(deserializer)
688 }
689
690 self::assert_eq!(de("0x2a").unwrap(), 42);
691 self::assert_eq!(de("0x0").unwrap(), 0);
692 for invalid in ["", "0x", "2a", "0xzz", "cthulhu"] {
693 assert!(de(invalid).is_err(), "{invalid:?} should be rejected");
694 }
695 }
696
697 #[quickcheck]
698 fn hexify_bytes_matches_legacy_h64(value: u64) -> bool {
699 matches_legacy_lowerhex(ethereum_types::H64::from_low_u64_be(value))
700 }
701
702 #[quickcheck]
703 fn hexify_bytes_matches_legacy_h160(bytes: Vec<u8>) -> bool {
704 matches_legacy_lowerhex(ethereum_types::H160::from(filled::<20>(bytes)))
705 }
706
707 #[quickcheck]
708 fn hexify_bytes_matches_legacy_bloom(bytes: Vec<u8>) -> bool {
709 matches_legacy_lowerhex(ethereum_types::Bloom::from(filled::<256>(bytes)))
710 }
711
712 #[test]
713 fn test_hexify_vec_bytes_serialize() {
714 let cases = [(vec![], "0x"), (vec![0], "0x00"), (vec![42, 66], "0x2a42")];
715
716 for (input, expected) in cases.into_iter() {
717 let hexify = HexifyVecBytesTest { value: input };
718 let serialized = serde_json::to_string(&hexify).unwrap();
719 self::assert_eq!(serialized, format!("{{\"value\":\"{}\"}}", expected));
720 }
721 }
722
723 #[test]
724 fn test_hexify_vec_bytes_deserialize() {
725 let cases = [
726 ("0x", vec![]),
727 ("0x0", vec![0]),
728 ("0xF", vec![15]),
729 ("0x2a42", vec![42, 66]),
730 ("0x2A42", vec![42, 66]),
731 ];
732
733 for (input, expected) in cases.into_iter() {
734 let deserializer: StringDeserializer<SerdeError> =
735 String::from_str(input).unwrap().into_deserializer();
736 let deserialized = hexify_vec_bytes::deserialize(deserializer).unwrap();
737 self::assert_eq!(deserialized, expected);
738 }
739
740 let fail_cases = ["cthulhu", "x", "0xazathoth"];
741 for input in fail_cases.into_iter() {
742 let deserializer: StringDeserializer<SerdeError> =
743 String::from_str(input).unwrap().into_deserializer();
744 let deserialized = hexify_vec_bytes::deserialize(deserializer);
745 assert!(deserialized.is_err());
746 }
747 }
748}