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 ($module:path: $ty:ident: $($version:literal),+ $(,)?) => {
251 $(
252 pastey::paste! {
253 #[test]
254 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
255 use super::*;
256 assert_all_snapshots::<$module::[<v $version>]::$ty>();
257 }
258 }
259 )+
260 };
261
262 ($module:path: $nested_path:path: $ty:ident: $($version:literal),+ $(,)?) => {
263 $(
264 pastey::paste! {
265 #[test]
266 fn [<snapshots_ $module _v $version _ $ty:lower>]() {
267 use super::*;
268 assert_all_snapshots::<$module::[<v $version>]::$nested_path::$ty>();
269 }
270 }
271 )+
272 };
273}
274
275#[cfg(any(test, doc))]
276pub fn assert_all_snapshots<T>()
277where
278 T: HasLotusJson + PartialEq + std::fmt::Debug + Clone,
279{
280 let snapshots = T::snapshots();
281 assert!(!snapshots.is_empty());
282 for (lotus_json, val) in snapshots {
283 assert_one_snapshot(lotus_json, val);
284 }
285}
286
287#[cfg(test)]
288pub fn assert_one_snapshot<T>(lotus_json: serde_json::Value, val: T)
289where
290 T: HasLotusJson + PartialEq + std::fmt::Debug + Clone,
291{
292 let serialized = val.clone().into_lotus_json_value().unwrap();
294 assert_eq!(
295 serialized.to_string(),
296 lotus_json.to_string(),
297 "snapshot failed for {}",
298 std::any::type_name::<T>()
299 );
300
301 let deserialized = match serde_json::from_value::<T::LotusJson>(lotus_json.clone()) {
303 Ok(lotus_json) => T::from_lotus_json(lotus_json),
304 Err(e) => panic!(
305 "couldn't deserialize a {} from {}: {e}",
306 std::any::type_name::<T::LotusJson>(),
307 lotus_json
308 ),
309 };
310 assert_eq!(deserialized, val);
311}
312
313#[cfg(any(test, doc))]
314pub fn assert_unchanged_via_json<T>(val: T)
315where
316 T: HasLotusJson + Clone + PartialEq + std::fmt::Debug,
317 T::LotusJson: Serialize + serde::de::DeserializeOwned,
318{
319 let temp = val.clone().into_lotus_json();
323 let temp = serde_json::to_value(temp).unwrap();
325 let temp = serde_json::from_value::<T::LotusJson>(temp).unwrap();
327 let temp = T::from_lotus_json(temp);
329
330 assert_eq!(val, temp);
331}
332
333pub mod stringify {
335 use super::*;
336
337 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
338 where
339 T: Display,
340 S: Serializer,
341 {
342 serializer.collect_str(value)
343 }
344
345 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
346 where
347 T: FromStr,
348 T::Err: Display,
349 D: Deserializer<'de>,
350 {
351 String::deserialize(deserializer)?
352 .parse()
353 .map_err(serde::de::Error::custom)
354 }
355}
356
357pub mod hexify_bytes {
359 use super::*;
360
361 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
362 where
363 T: Display + std::fmt::LowerHex,
364 S: Serializer,
365 {
366 serializer.serialize_str(&format!("{value:#x}"))
369 }
370
371 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
372 where
373 T: FromStr,
374 T::Err: Display,
375 D: Deserializer<'de>,
376 {
377 String::deserialize(deserializer)?
378 .parse()
379 .map_err(serde::de::Error::custom)
380 }
381}
382
383pub mod hexify_vec_bytes {
384 use super::*;
385 use std::borrow::Cow;
386
387 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
388 where
389 S: Serializer,
390 {
391 let mut s = vec![0; 2 + value.len() * 2];
392 s.get_mut(0..2)
393 .expect("len is correct")
394 .copy_from_slice(b"0x");
395 hex::encode_to_slice(value, s.get_mut(2..).expect("len is correct"))
396 .map_err(serde::ser::Error::custom)?;
397 serializer.serialize_str(std::str::from_utf8(&s).expect("valid utf8"))
398 }
399
400 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
401 where
402 D: Deserializer<'de>,
403 {
404 let s = String::deserialize(deserializer)?;
405 let s = Cow::from(s.strip_prefix("0x").unwrap_or(&s));
406
407 let s = if s.len() % 2 == 0 {
410 s
411 } else {
412 let mut s = s.into_owned();
413 s.insert(0, '0');
414 Cow::Owned(s)
415 };
416
417 hex::decode(s.as_ref()).map_err(serde::de::Error::custom)
418 }
419}
420
421pub mod hexify {
423 use super::*;
424 use num_traits::Num;
425 use serde::{Deserializer, Serializer};
426
427 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
428 where
429 T: Num + std::fmt::LowerHex,
430 S: Serializer,
431 {
432 serializer.serialize_str(format!("{value:#x}").as_str())
433 }
434
435 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
436 where
437 T: Num,
438 <T as Num>::FromStrRadixErr: std::fmt::Display,
439 D: Deserializer<'de>,
440 {
441 let s = String::deserialize(deserializer)?;
442 #[allow(clippy::indexing_slicing)]
443 if s.len() > 2 && &s[..2] == "0x" {
444 T::from_str_radix(&s[2..], 16).map_err(serde::de::Error::custom)
445 } else {
446 Err(serde::de::Error::custom("Invalid hex"))
447 }
448 }
449}
450
451pub mod base64_standard {
453 use super::*;
454
455 use base64::engine::{Engine as _, general_purpose::STANDARD};
456
457 pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
458 where
459 S: Serializer,
460 {
461 STANDARD.encode(value).serialize(serializer)
462 }
463
464 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
465 where
466 D: Deserializer<'de>,
467 {
468 STANDARD
469 .decode(String::deserialize(deserializer)?)
470 .map_err(serde::de::Error::custom)
471 }
472}
473
474pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
476where
477 S: Serializer,
478 T: HasLotusJson + Clone,
479{
480 value.clone().into_lotus_json().serialize(serializer)
481}
482
483pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
485where
486 D: Deserializer<'de>,
487 T: HasLotusJson,
488{
489 Ok(T::from_lotus_json(Deserialize::deserialize(deserializer)?))
490}
491
492#[derive(
494 Debug, Deserialize, From, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Clone,
495)]
496#[serde(bound = "T: HasLotusJson + Clone", transparent)]
497pub struct LotusJson<T>(#[serde(with = "self")] pub T);
498
499impl<T> JsonSchema for LotusJson<T>
500where
501 T: HasLotusJson,
502 T::LotusJson: JsonSchema,
503{
504 fn schema_name() -> std::borrow::Cow<'static, str> {
505 T::LotusJson::schema_name()
506 }
507
508 fn schema_id() -> std::borrow::Cow<'static, str> {
509 T::LotusJson::schema_id()
510 }
511
512 fn json_schema(g: &mut SchemaGenerator) -> Schema {
513 T::LotusJson::json_schema(g)
514 }
515}
516
517impl<T> LotusJson<T> {
518 pub fn into_inner(self) -> T {
519 self.0
520 }
521}
522
523macro_rules! lotus_json_with_self {
524 ($($domain_ty:ty),* $(,)?) => {
525 $(
526 impl $crate::lotus_json::HasLotusJson for $domain_ty {
527 type LotusJson = Self;
528 #[cfg(test)]
529 fn snapshots() -> Vec<(serde_json::Value, Self)> {
530 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
531 }
532 fn into_lotus_json(self) -> Self::LotusJson {
533 self
534 }
535 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
536 lotus_json
537 }
538 }
539 )*
540 }
541}
542pub(crate) use lotus_json_with_self;
543
544lotus_json_with_self!(
545 u32,
546 u64,
547 i64,
548 f64,
549 String,
550 chrono::DateTime<chrono::Utc>,
551 serde_json::Value,
552 (),
553 std::path::PathBuf,
554 bool,
555 DeadlineInfo,
556 PaddedPieceSize,
557 Uuid,
558 std::num::NonZeroUsize,
559);
560
561mod fixme {
562 use super::*;
563
564 impl<T: HasLotusJson> HasLotusJson for (T,) {
565 type LotusJson = (T::LotusJson,);
566 #[cfg(test)]
567 fn snapshots() -> Vec<(serde_json::Value, Self)> {
568 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
569 }
570 fn into_lotus_json(self) -> Self::LotusJson {
571 (self.0.into_lotus_json(),)
572 }
573 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
574 (HasLotusJson::from_lotus_json(lotus_json.0),)
575 }
576 }
577
578 impl<A: HasLotusJson, B: HasLotusJson> HasLotusJson for (A, B) {
579 type LotusJson = (A::LotusJson, B::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(), self.1.into_lotus_json())
586 }
587 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
588 (
589 HasLotusJson::from_lotus_json(lotus_json.0),
590 HasLotusJson::from_lotus_json(lotus_json.1),
591 )
592 }
593 }
594
595 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson> HasLotusJson for (A, B, C) {
596 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson);
597 #[cfg(test)]
598 fn snapshots() -> Vec<(serde_json::Value, Self)> {
599 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
600 }
601 fn into_lotus_json(self) -> Self::LotusJson {
602 (
603 self.0.into_lotus_json(),
604 self.1.into_lotus_json(),
605 self.2.into_lotus_json(),
606 )
607 }
608 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
609 (
610 HasLotusJson::from_lotus_json(lotus_json.0),
611 HasLotusJson::from_lotus_json(lotus_json.1),
612 HasLotusJson::from_lotus_json(lotus_json.2),
613 )
614 }
615 }
616
617 impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson, D: HasLotusJson> HasLotusJson
618 for (A, B, C, D)
619 {
620 type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson, D::LotusJson);
621 #[cfg(test)]
622 fn snapshots() -> Vec<(serde_json::Value, Self)> {
623 unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
624 }
625 fn into_lotus_json(self) -> Self::LotusJson {
626 (
627 self.0.into_lotus_json(),
628 self.1.into_lotus_json(),
629 self.2.into_lotus_json(),
630 self.3.into_lotus_json(),
631 )
632 }
633 fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
634 (
635 HasLotusJson::from_lotus_json(lotus_json.0),
636 HasLotusJson::from_lotus_json(lotus_json.1),
637 HasLotusJson::from_lotus_json(lotus_json.2),
638 HasLotusJson::from_lotus_json(lotus_json.3),
639 )
640 }
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647 use ipld_core::serde::SerdeError;
648 use serde::de::{IntoDeserializer, value::StringDeserializer};
649
650 #[derive(Debug, Deserialize, Serialize, PartialEq)]
651 struct HexifyVecBytesTest {
652 #[serde(with = "hexify_vec_bytes")]
653 value: Vec<u8>,
654 }
655
656 #[test]
657 fn test_hexify_vec_bytes_serialize() {
658 let cases = [(vec![], "0x"), (vec![0], "0x00"), (vec![42, 66], "0x2a42")];
659
660 for (input, expected) in cases.into_iter() {
661 let hexify = HexifyVecBytesTest { value: input };
662 let serialized = serde_json::to_string(&hexify).unwrap();
663 self::assert_eq!(serialized, format!("{{\"value\":\"{}\"}}", expected));
664 }
665 }
666
667 #[test]
668 fn test_hexify_vec_bytes_deserialize() {
669 let cases = [
670 ("0x", vec![]),
671 ("0x0", vec![0]),
672 ("0xF", vec![15]),
673 ("0x2a42", vec![42, 66]),
674 ("0x2A42", vec![42, 66]),
675 ];
676
677 for (input, expected) in cases.into_iter() {
678 let deserializer: StringDeserializer<SerdeError> =
679 String::from_str(input).unwrap().into_deserializer();
680 let deserialized = hexify_vec_bytes::deserialize(deserializer).unwrap();
681 self::assert_eq!(deserialized, expected);
682 }
683
684 let fail_cases = ["cthulhu", "x", "0xazathoth"];
685 for input in fail_cases.into_iter() {
686 let deserializer: StringDeserializer<SerdeError> =
687 String::from_str(input).unwrap().into_deserializer();
688 let deserialized = hexify_vec_bytes::deserialize(deserializer);
689 assert!(deserialized.is_err());
690 }
691 }
692}