1use crate::shim::actors::miner::DeadlineInfo;
125use derive_more::From;
126use fvm_shared4::piece::PaddedPieceSize;
127use schemars::{JsonSchema, Schema, SchemaGenerator};
128use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
129#[cfg(test)]
130use serde_json::json;
131use std::{fmt::Display, str::FromStr};
132use uuid::Uuid;
133#[cfg(test)]
134use {pretty_assertions::assert_eq, quickcheck::quickcheck};
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 election_proof for crate::blocks::ElectionProof,
200 extended_sector_info for crate::shim::sector::ExtendedSectorInfo,
201 gossip_block for crate::blocks::GossipBlock,
202 key_info for crate::key_management::KeyInfo,
203 message for crate::shim::message::Message,
204 po_st_proof for crate::shim::sector::PoStProof,
205 registered_po_st_proof for crate::shim::sector::RegisteredPoStProof,
206 registered_seal_proof for crate::shim::sector::RegisteredSealProof,
207 sector_info for crate::shim::sector::SectorInfo,
208 sector_size for crate::shim::sector::SectorSize,
209 signature for crate::shim::crypto::Signature,
210 signature_type for crate::shim::crypto::SignatureType,
211 signed_message for crate::message::SignedMessage,
212 ticket for crate::blocks::Ticket,
213 tipset_keys for crate::blocks::TipsetKey,
214 token_amount for crate::shim::econ::TokenAmount,
215 vec_u8 for Vec<u8>,
216 vrf_proof for crate::blocks::VRFProof,
217);
218
219mod actors;
222mod allocation;
223mod arc;
224mod beneficiary_term; mod bit_field; mod bytecode_hash;
227mod entry;
228mod filter_estimate;
229mod hash_map;
230mod ipld; mod miner_info; mod miner_power; mod nonempty; mod opt; mod padded_piece_size;
236mod pending_beneficiary_change; mod power_claim; mod raw_bytes; mod receipt; mod token_state;
241mod tombstone;
242mod transient_data;
243mod vec; mod verifreg_claim;
245
246pub use vec::*;
247
248#[macro_export]
249macro_rules! test_snapshots {
250 ($ty:ty) => {
251 pastey::paste! {
252 #[test]
253 fn [<snapshots_ $ty:snake>]() {
254 use super::*;
255 assert_all_snapshots::<$ty>();
256 }
257 }
258 };
259
260 ($module:path: $ty:ident: $($version:literal),+ $(,)?) => {
261 $(
262 pastey::paste! {
263 #[test]
264 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
265 use super::*;
266 assert_all_snapshots::<$module::[<v $version>]::$ty>();
267 }
268 }
269 )+
270 };
271
272 ($module:path: $nested_path:path: $ty:ident: $($version:literal),+ $(,)?) => {
273 $(
274 pastey::paste! {
275 #[test]
276 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
277 use super::*;
278 assert_all_snapshots::<$module::[<v $version>]::$nested_path::$ty>();
279 }
280 }
281 )+
282 };
283}
284
285#[cfg(any(test, doc))]
286pub fn assert_all_snapshots<T>()
287where
288 T: HasLotusJson,
289 <T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
290{
291 let snapshots = T::snapshots();
292 assert!(!snapshots.is_empty());
293 for (lotus_json, val) in snapshots {
294 assert_one_snapshot(lotus_json, val);
295 }
296}
297
298#[cfg(test)]
299pub fn assert_one_snapshot<T>(lotus_json: serde_json::Value, val: T)
300where
301 T: HasLotusJson,
302 <T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
303{
304 let val_lotus_json = val.into_lotus_json();
306 let serialized = serde_json::to_value(&val_lotus_json).unwrap();
307 assert_eq!(
308 serialized.to_string(),
309 lotus_json.to_string(),
310 "snapshot failed for {}",
311 std::any::type_name::<T>()
312 );
313
314 let deserialized = match serde_json::from_value::<T::LotusJson>(lotus_json.clone()) {
317 Ok(lotus_json) => T::from_lotus_json(lotus_json).into_lotus_json(),
318 Err(e) => panic!(
319 "couldn't deserialize a {} from {}: {e}",
320 std::any::type_name::<T::LotusJson>(),
321 lotus_json
322 ),
323 };
324 assert_eq!(deserialized, val_lotus_json);
325}
326
327#[cfg(any(test, doc))]
328pub fn assert_unchanged_via_json<T>(val: T)
329where
330 T: HasLotusJson + Clone + PartialEq + std::fmt::Debug,
331 T::LotusJson: Serialize + serde::de::DeserializeOwned,
332{
333 let temp = val.clone().into_lotus_json();
337 let temp = serde_json::to_value(temp).unwrap();
339 let temp = serde_json::from_value::<T::LotusJson>(temp).unwrap();
341 let temp = T::from_lotus_json(temp);
343
344 assert_eq!(val, temp);
345}
346
347pub mod stringify {
349 use super::*;
350
351 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
352 where
353 T: Display,
354 S: Serializer,
355 {
356 serializer.collect_str(value)
357 }
358
359 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
360 where
361 T: FromStr,
362 T::Err: Display,
363 D: Deserializer<'de>,
364 {
365 String::deserialize(deserializer)?
366 .parse()
367 .map_err(serde::de::Error::custom)
368 }
369}
370
371pub mod hexify_bytes {
373 use super::*;
374
375 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
376 where
377 T: Display + std::fmt::LowerHex,
378 S: Serializer,
379 {
380 serializer.serialize_str(&format!("{value:#x}"))
383 }
384
385 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
386 where
387 T: FromStr,
388 T::Err: Display,
389 D: Deserializer<'de>,
390 {
391 String::deserialize(deserializer)?
392 .parse()
393 .map_err(serde::de::Error::custom)
394 }
395}
396
397pub mod hexify_vec_bytes {
398 use super::*;
399 use std::borrow::Cow;
400
401 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
402 where
403 S: Serializer,
404 {
405 let mut s = vec![0; 2 + value.len() * 2];
406 s.get_mut(0..2)
407 .expect("len is correct")
408 .copy_from_slice(b"0x");
409 hex::encode_to_slice(value, s.get_mut(2..).expect("len is correct"))
410 .map_err(serde::ser::Error::custom)?;
411 serializer.serialize_str(std::str::from_utf8(&s).expect("valid utf8"))
412 }
413
414 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
415 where
416 D: Deserializer<'de>,
417 {
418 let s = String::deserialize(deserializer)?;
419 let s = Cow::from(s.strip_prefix("0x").unwrap_or(&s));
420
421 let s = if s.len() % 2 == 0 {
424 s
425 } else {
426 let mut s = s.into_owned();
427 s.insert(0, '0');
428 Cow::Owned(s)
429 };
430
431 hex::decode(s.as_ref()).map_err(serde::de::Error::custom)
432 }
433}
434
435pub mod hexify {
437 use super::*;
438 use num_traits::Num;
439 use serde::{Deserializer, Serializer};
440
441 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
442 where
443 T: Num + std::fmt::LowerHex,
444 S: Serializer,
445 {
446 serializer.serialize_str(format!("{value:#x}").as_str())
447 }
448
449 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
450 where
451 T: Num,
452 <T as Num>::FromStrRadixErr: std::fmt::Display,
453 D: Deserializer<'de>,
454 {
455 let s = String::deserialize(deserializer)?;
456 #[allow(clippy::indexing_slicing)]
457 if s.len() > 2 && &s[..2] == "0x" {
458 T::from_str_radix(&s[2..], 16).map_err(serde::de::Error::custom)
459 } else {
460 Err(serde::de::Error::custom("Invalid hex"))
461 }
462 }
463}
464
465pub mod base64_standard {
467 use super::*;
468
469 use base64::engine::{Engine as _, general_purpose::STANDARD};
470
471 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
472 where
473 S: Serializer,
474 {
475 STANDARD.encode(value).serialize(serializer)
476 }
477
478 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
479 where
480 D: Deserializer<'de>,
481 {
482 STANDARD
483 .decode(String::deserialize(deserializer)?)
484 .map_err(serde::de::Error::custom)
485 }
486}
487
488pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
490where
491 S: Serializer,
492 T: HasLotusJson + Clone,
493{
494 value.clone().into_lotus_json().serialize(serializer)
495}
496
497pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
499where
500 D: Deserializer<'de>,
501 T: HasLotusJson,
502{
503 Ok(T::from_lotus_json(Deserialize::deserialize(deserializer)?))
504}
505
506#[derive(
508 Debug, Deserialize, From, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Clone,
509)]
510#[serde(bound = "T: HasLotusJson + Clone", transparent)]
511pub struct LotusJson<T>(#[serde(with = "self")] pub T);
512
513impl<T> JsonSchema for LotusJson<T>
514where
515 T: HasLotusJson,
516 T::LotusJson: JsonSchema,
517{
518 fn schema_name() -> std::borrow::Cow<'static, str> {
519 T::LotusJson::schema_name()
520 }
521
522 fn schema_id() -> std::borrow::Cow<'static, str> {
523 T::LotusJson::schema_id()
524 }
525
526 fn json_schema(g: &mut SchemaGenerator) -> Schema {
527 T::LotusJson::json_schema(g)
528 }
529}
530
531impl<T> LotusJson<T> {
532 pub fn into_inner(self) -> T {
533 self.0
534 }
535}
536
537macro_rules! lotus_json_with_self {
538 ($($domain_ty:ty),* $(,)?) => {
539 $(
540 impl $crate::lotus_json::HasLotusJson for $domain_ty {
541 type LotusJson = Self;
542 #[cfg(test)]
543 fn snapshots() -> Vec<(serde_json::Value, Self)> {
544 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
545 }
546 fn into_lotus_json(self) -> Self::LotusJson {
547 self
548 }
549 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
550 lotus_json
551 }
552 }
553 )*
554 }
555}
556pub(crate) use lotus_json_with_self;
557
558lotus_json_with_self!(
559 u32,
560 u64,
561 i64,
562 f64,
563 String,
564 chrono::DateTime<chrono::Utc>,
565 serde_json::Value,
566 (),
567 std::path::PathBuf,
568 bool,
569 DeadlineInfo,
570 PaddedPieceSize,
571 Uuid,
572 std::num::NonZeroUsize,
573);
574
575mod fixme {
576 use super::*;
577
578 impl<T: HasLotusJson> HasLotusJson for (T,) {
579 type LotusJson = (T::LotusJson,);
580 #[cfg(test)]
581 fn snapshots() -> Vec<(serde_json::Value, Self)> {
582 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
583 }
584 fn into_lotus_json(self) -> Self::LotusJson {
585 (self.0.into_lotus_json(),)
586 }
587 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
588 (HasLotusJson::from_lotus_json(lotus_json.0),)
589 }
590 }
591
592 impl<A: HasLotusJson, B: HasLotusJson> HasLotusJson for (A, B) {
593 type LotusJson = (A::LotusJson, B::LotusJson);
594 #[cfg(test)]
595 fn snapshots() -> Vec<(serde_json::Value, Self)> {
596 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
597 }
598 fn into_lotus_json(self) -> Self::LotusJson {
599 (self.0.into_lotus_json(), self.1.into_lotus_json())
600 }
601 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
602 (
603 HasLotusJson::from_lotus_json(lotus_json.0),
604 HasLotusJson::from_lotus_json(lotus_json.1),
605 )
606 }
607 }
608
609 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson> HasLotusJson for (A, B, C) {
610 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson);
611 #[cfg(test)]
612 fn snapshots() -> Vec<(serde_json::Value, Self)> {
613 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
614 }
615 fn into_lotus_json(self) -> Self::LotusJson {
616 (
617 self.0.into_lotus_json(),
618 self.1.into_lotus_json(),
619 self.2.into_lotus_json(),
620 )
621 }
622 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
623 (
624 HasLotusJson::from_lotus_json(lotus_json.0),
625 HasLotusJson::from_lotus_json(lotus_json.1),
626 HasLotusJson::from_lotus_json(lotus_json.2),
627 )
628 }
629 }
630
631 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson, D: HasLotusJson> HasLotusJson
632 for (A, B, C, D)
633 {
634 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson, D::LotusJson);
635 #[cfg(test)]
636 fn snapshots() -> Vec<(serde_json::Value, Self)> {
637 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
638 }
639 fn into_lotus_json(self) -> Self::LotusJson {
640 (
641 self.0.into_lotus_json(),
642 self.1.into_lotus_json(),
643 self.2.into_lotus_json(),
644 self.3.into_lotus_json(),
645 )
646 }
647 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
648 (
649 HasLotusJson::from_lotus_json(lotus_json.0),
650 HasLotusJson::from_lotus_json(lotus_json.1),
651 HasLotusJson::from_lotus_json(lotus_json.2),
652 HasLotusJson::from_lotus_json(lotus_json.3),
653 )
654 }
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use ipld_core::serde::SerdeError;
662 use serde::de::{IntoDeserializer, value::StringDeserializer};
663
664 #[derive(Debug, Deserialize, Serialize, PartialEq)]
665 struct HexifyVecBytesTest {
666 #[serde(with = "hexify_vec_bytes")]
667 value: Vec<u8>,
668 }
669
670 #[test]
671 fn test_hexify_vec_bytes_serialize() {
672 let cases = [(vec![], "0x"), (vec![0], "0x00"), (vec![42, 66], "0x2a42")];
673
674 for (input, expected) in cases.into_iter() {
675 let hexify = HexifyVecBytesTest { value: input };
676 let serialized = serde_json::to_string(&hexify).unwrap();
677 self::assert_eq!(serialized, format!("{{\"value\":\"{}\"}}", expected));
678 }
679 }
680
681 #[test]
682 fn test_hexify_vec_bytes_deserialize() {
683 let cases = [
684 ("0x", vec![]),
685 ("0x0", vec![0]),
686 ("0xF", vec![15]),
687 ("0x2a42", vec![42, 66]),
688 ("0x2A42", vec![42, 66]),
689 ];
690
691 for (input, expected) in cases.into_iter() {
692 let deserializer: StringDeserializer<SerdeError> =
693 String::from_str(input).unwrap().into_deserializer();
694 let deserialized = hexify_vec_bytes::deserialize(deserializer).unwrap();
695 self::assert_eq!(deserialized, expected);
696 }
697
698 let fail_cases = ["cthulhu", "x", "0xazathoth"];
699 for input in fail_cases.into_iter() {
700 let deserializer: StringDeserializer<SerdeError> =
701 String::from_str(input).unwrap().into_deserializer();
702 let deserialized = hexify_vec_bytes::deserialize(deserializer);
703 assert!(deserialized.is_err());
704 }
705 }
706}