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: Display + std::fmt::LowerHex,
379 S: Serializer,
380 {
381 serializer.serialize_str(&format!("{value:#x}"))
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 let mut s = vec![0; 2 + value.len() * 2];
407 s.get_mut(0..2)
408 .expect("len is correct")
409 .copy_from_slice(b"0x");
410 hex::encode_to_slice(value, s.get_mut(2..).expect("len is correct"))
411 .map_err(serde::ser::Error::custom)?;
412 serializer.serialize_str(std::str::from_utf8(&s).expect("valid utf8"))
413 }
414
415 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
416 where
417 D: Deserializer<'de>,
418 {
419 let s = String::deserialize(deserializer)?;
420 let s = Cow::from(s.strip_prefix("0x").unwrap_or(&s));
421
422 let s = if s.len() % 2 == 0 {
425 s
426 } else {
427 let mut s = s.into_owned();
428 s.insert(0, '0');
429 Cow::Owned(s)
430 };
431
432 hex::decode(s.as_ref()).map_err(serde::de::Error::custom)
433 }
434}
435
436pub mod hexify {
438 use super::*;
439 use num_traits::Num;
440 use serde::{Deserializer, Serializer};
441
442 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
443 where
444 T: Num + std::fmt::LowerHex,
445 S: Serializer,
446 {
447 serializer.serialize_str(format!("{value:#x}").as_str())
448 }
449
450 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
451 where
452 T: Num,
453 <T as Num>::FromStrRadixErr: std::fmt::Display,
454 D: Deserializer<'de>,
455 {
456 let s = String::deserialize(deserializer)?;
457 #[allow(clippy::indexing_slicing)]
458 if s.len() > 2 && &s[..2] == "0x" {
459 T::from_str_radix(&s[2..], 16).map_err(serde::de::Error::custom)
460 } else {
461 Err(serde::de::Error::custom("Invalid hex"))
462 }
463 }
464}
465
466pub mod base64_standard {
468 use super::*;
469
470 use base64::engine::{Engine as _, general_purpose::STANDARD};
471
472 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
473 where
474 S: Serializer,
475 {
476 STANDARD.encode(value).serialize(serializer)
477 }
478
479 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
480 where
481 D: Deserializer<'de>,
482 {
483 STANDARD
484 .decode(String::deserialize(deserializer)?)
485 .map_err(serde::de::Error::custom)
486 }
487}
488
489pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
491where
492 S: Serializer,
493 T: HasLotusJson + Clone,
494{
495 value.clone().into_lotus_json().serialize(serializer)
496}
497
498pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
500where
501 D: Deserializer<'de>,
502 T: HasLotusJson,
503{
504 Ok(T::from_lotus_json(Deserialize::deserialize(deserializer)?))
505}
506
507#[derive(
509 Debug, Deserialize, From, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Clone,
510)]
511#[serde(bound = "T: HasLotusJson + Clone", transparent)]
512pub struct LotusJson<T>(#[serde(with = "self")] pub T);
513
514impl<T> JsonSchema for LotusJson<T>
515where
516 T: HasLotusJson,
517 T::LotusJson: JsonSchema,
518{
519 fn schema_name() -> std::borrow::Cow<'static, str> {
520 T::LotusJson::schema_name()
521 }
522
523 fn schema_id() -> std::borrow::Cow<'static, str> {
524 T::LotusJson::schema_id()
525 }
526
527 fn json_schema(g: &mut SchemaGenerator) -> Schema {
528 T::LotusJson::json_schema(g)
529 }
530}
531
532impl<T> LotusJson<T> {
533 pub fn into_inner(self) -> T {
534 self.0
535 }
536}
537
538macro_rules! lotus_json_with_self {
539 ($($domain_ty:ty),* $(,)?) => {
540 $(
541 impl $crate::lotus_json::HasLotusJson for $domain_ty {
542 type LotusJson = Self;
543 #[cfg(test)]
544 fn snapshots() -> Vec<(serde_json::Value, Self)> {
545 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
546 }
547 fn into_lotus_json(self) -> Self::LotusJson {
548 self
549 }
550 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
551 lotus_json
552 }
553 }
554 )*
555 }
556}
557pub(crate) use lotus_json_with_self;
558
559lotus_json_with_self!(
560 u32,
561 u64,
562 i64,
563 f64,
564 String,
565 chrono::DateTime<chrono::Utc>,
566 serde_json::Value,
567 (),
568 std::path::PathBuf,
569 bool,
570 DeadlineInfo,
571 PaddedPieceSize,
572 Uuid,
573 std::num::NonZeroUsize,
574);
575
576mod fixme {
577 use super::*;
578
579 impl<T: HasLotusJson> HasLotusJson for (T,) {
580 type LotusJson = (T::LotusJson,);
581 #[cfg(test)]
582 fn snapshots() -> Vec<(serde_json::Value, Self)> {
583 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
584 }
585 fn into_lotus_json(self) -> Self::LotusJson {
586 (self.0.into_lotus_json(),)
587 }
588 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
589 (HasLotusJson::from_lotus_json(lotus_json.0),)
590 }
591 }
592
593 impl<A: HasLotusJson, B: HasLotusJson> HasLotusJson for (A, B) {
594 type LotusJson = (A::LotusJson, B::LotusJson);
595 #[cfg(test)]
596 fn snapshots() -> Vec<(serde_json::Value, Self)> {
597 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
598 }
599 fn into_lotus_json(self) -> Self::LotusJson {
600 (self.0.into_lotus_json(), self.1.into_lotus_json())
601 }
602 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
603 (
604 HasLotusJson::from_lotus_json(lotus_json.0),
605 HasLotusJson::from_lotus_json(lotus_json.1),
606 )
607 }
608 }
609
610 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson> HasLotusJson for (A, B, C) {
611 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson);
612 #[cfg(test)]
613 fn snapshots() -> Vec<(serde_json::Value, Self)> {
614 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
615 }
616 fn into_lotus_json(self) -> Self::LotusJson {
617 (
618 self.0.into_lotus_json(),
619 self.1.into_lotus_json(),
620 self.2.into_lotus_json(),
621 )
622 }
623 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
624 (
625 HasLotusJson::from_lotus_json(lotus_json.0),
626 HasLotusJson::from_lotus_json(lotus_json.1),
627 HasLotusJson::from_lotus_json(lotus_json.2),
628 )
629 }
630 }
631
632 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson, D: HasLotusJson> HasLotusJson
633 for (A, B, C, D)
634 {
635 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson, D::LotusJson);
636 #[cfg(test)]
637 fn snapshots() -> Vec<(serde_json::Value, Self)> {
638 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
639 }
640 fn into_lotus_json(self) -> Self::LotusJson {
641 (
642 self.0.into_lotus_json(),
643 self.1.into_lotus_json(),
644 self.2.into_lotus_json(),
645 self.3.into_lotus_json(),
646 )
647 }
648 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
649 (
650 HasLotusJson::from_lotus_json(lotus_json.0),
651 HasLotusJson::from_lotus_json(lotus_json.1),
652 HasLotusJson::from_lotus_json(lotus_json.2),
653 HasLotusJson::from_lotus_json(lotus_json.3),
654 )
655 }
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use ipld_core::serde::SerdeError;
663 use serde::de::{IntoDeserializer, value::StringDeserializer};
664
665 #[derive(Debug, Deserialize, Serialize, PartialEq)]
666 struct HexifyVecBytesTest {
667 #[serde(with = "hexify_vec_bytes")]
668 value: Vec<u8>,
669 }
670
671 #[test]
672 fn test_hexify_vec_bytes_serialize() {
673 let cases = [(vec![], "0x"), (vec![0], "0x00"), (vec![42, 66], "0x2a42")];
674
675 for (input, expected) in cases.into_iter() {
676 let hexify = HexifyVecBytesTest { value: input };
677 let serialized = serde_json::to_string(&hexify).unwrap();
678 self::assert_eq!(serialized, format!("{{\"value\":\"{}\"}}", expected));
679 }
680 }
681
682 #[test]
683 fn test_hexify_vec_bytes_deserialize() {
684 let cases = [
685 ("0x", vec![]),
686 ("0x0", vec![0]),
687 ("0xF", vec![15]),
688 ("0x2a42", vec![42, 66]),
689 ("0x2A42", vec![42, 66]),
690 ];
691
692 for (input, expected) in cases.into_iter() {
693 let deserializer: StringDeserializer<SerdeError> =
694 String::from_str(input).unwrap().into_deserializer();
695 let deserialized = hexify_vec_bytes::deserialize(deserializer).unwrap();
696 self::assert_eq!(deserialized, expected);
697 }
698
699 let fail_cases = ["cthulhu", "x", "0xazathoth"];
700 for input in fail_cases.into_iter() {
701 let deserializer: StringDeserializer<SerdeError> =
702 String::from_str(input).unwrap().into_deserializer();
703 let deserialized = hexify_vec_bytes::deserialize(deserializer);
704 assert!(deserialized.is_err());
705 }
706 }
707}