use crate::shim::actors::miner::DeadlineInfo;
use derive_more::From;
use fvm_shared4::piece::PaddedPieceSize;
#[cfg(test)]
use pretty_assertions::assert_eq;
use schemars::{JsonSchema, Schema, SchemaGenerator};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
#[cfg(test)]
use serde_json::json;
use std::{fmt::Display, str::FromStr};
use uuid::Uuid;
pub trait HasLotusJson: Sized {
type LotusJson: Serialize + DeserializeOwned;
#[cfg(test)]
fn snapshots() -> Vec<(serde_json::Value, Self)>;
fn into_lotus_json(self) -> Self::LotusJson;
fn from_lotus_json(lotus_json: Self::LotusJson) -> Self;
fn into_lotus_json_value(self) -> serde_json::Result<serde_json::Value> {
serde_json::to_value(self.into_lotus_json())
}
fn into_lotus_json_string(self) -> serde_json::Result<String> {
serde_json::to_string(&self.into_lotus_json())
}
fn into_lotus_json_string_pretty(self) -> serde_json::Result<String> {
serde_json::to_string_pretty(&self.into_lotus_json())
}
}
macro_rules! decl_and_test {
($($mod_name:ident for $domain_ty:ty),* $(,)?) => {
$(
mod $mod_name;
)*
#[test]
fn all_snapshots() {
$(
print!("test snapshots for {}...", std::any::type_name::<$domain_ty>());
std::io::Write::flush(&mut std::io::stdout()).unwrap();
assert_all_snapshots::<$domain_ty>();
println!("ok.");
)*
}
#[test]
fn all_quickchecks() {
$(
print!("quickcheck for {}...", std::any::type_name::<$domain_ty>());
std::io::Write::flush(&mut std::io::stdout()).unwrap();
::quickcheck::quickcheck(assert_unchanged_via_json::<$domain_ty> as fn(_));
println!("ok.");
)*
}
}
}
#[cfg(doc)]
pub(crate) use decl_and_test;
decl_and_test!(
actor_state for crate::shim::state_tree::ActorState,
address for crate::shim::address::Address,
beacon_entry for crate::beacon::BeaconEntry,
big_int for num::BigInt,
block_header for crate::blocks::CachingBlockHeader,
cid for ::cid::Cid,
duration for std::time::Duration,
election_proof for crate::blocks::ElectionProof,
extended_sector_info for crate::shim::sector::ExtendedSectorInfo,
gossip_block for crate::blocks::GossipBlock,
key_info for crate::key_management::KeyInfo,
message for crate::shim::message::Message,
po_st_proof for crate::shim::sector::PoStProof,
registered_po_st_proof for crate::shim::sector::RegisteredPoStProof,
registered_seal_proof for crate::shim::sector::RegisteredSealProof,
sector_info for crate::shim::sector::SectorInfo,
sector_size for crate::shim::sector::SectorSize,
signature for crate::shim::crypto::Signature,
signature_type for crate::shim::crypto::SignatureType,
signed_message for crate::message::SignedMessage,
ticket for crate::blocks::Ticket,
tipset_keys for crate::blocks::TipsetKey,
token_amount for crate::shim::econ::TokenAmount,
vec_u8 for Vec<u8>,
vrf_proof for crate::blocks::VRFProof,
);
mod actors;
mod allocation;
mod arc;
mod beneficiary_term; mod bit_field; mod bytecode_hash;
mod entry;
mod filter_estimate;
mod hash_map;
mod ipld; mod miner_info; mod miner_power; mod nonempty; mod opt; mod padded_piece_size;
mod pending_beneficiary_change; mod power_claim; mod raw_bytes; mod receipt; mod token_state;
mod tombstone;
mod transient_data;
mod vec; mod verifreg_claim;
pub use vec::*;
#[macro_export]
macro_rules! test_snapshots {
($ty:ty) => {
pastey::paste! {
#[test]
fn [<snapshots_ $ty:snake>]() {
use super::*;
assert_all_snapshots::<$ty>();
}
}
};
($module:path: $ty:ident: $($version:literal),+ $(,)?) => {
$(
pastey::paste! {
#[test]
fn [<snapshots_ $module _v $version _ $ty:lower>]() {
use super::*;
assert_all_snapshots::<$module::[<v $version>]::$ty>();
}
}
)+
};
($module:path: $nested_path:path: $ty:ident: $($version:literal),+ $(,)?) => {
$(
pastey::paste! {
#[test]
fn [<snapshots_ $module _v $version _ $ty:lower>]() {
use super::*;
assert_all_snapshots::<$module::[<v $version>]::$nested_path::$ty>();
}
}
)+
};
}
#[cfg(any(test, doc))]
pub fn assert_all_snapshots<T>()
where
T: HasLotusJson,
<T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
{
let snapshots = T::snapshots();
assert!(!snapshots.is_empty());
for (lotus_json, val) in snapshots {
assert_one_snapshot(lotus_json, val);
}
}
#[cfg(test)]
pub fn assert_one_snapshot<T>(lotus_json: serde_json::Value, val: T)
where
T: HasLotusJson,
<T as HasLotusJson>::LotusJson: PartialEq + std::fmt::Debug,
{
let val_lotus_json = val.into_lotus_json();
let serialized = serde_json::to_value(&val_lotus_json).unwrap();
assert_eq!(
serialized.to_string(),
lotus_json.to_string(),
"snapshot failed for {}",
std::any::type_name::<T>()
);
let deserialized = match serde_json::from_value::<T::LotusJson>(lotus_json.clone()) {
Ok(lotus_json) => T::from_lotus_json(lotus_json).into_lotus_json(),
Err(e) => panic!(
"couldn't deserialize a {} from {}: {e}",
std::any::type_name::<T::LotusJson>(),
lotus_json
),
};
assert_eq!(deserialized, val_lotus_json);
}
#[cfg(any(test, doc))]
pub fn assert_unchanged_via_json<T>(val: T)
where
T: HasLotusJson + Clone + PartialEq + std::fmt::Debug,
T::LotusJson: Serialize + serde::de::DeserializeOwned,
{
let temp = val.clone().into_lotus_json();
let temp = serde_json::to_value(temp).unwrap();
let temp = serde_json::from_value::<T::LotusJson>(temp).unwrap();
let temp = T::from_lotus_json(temp);
assert_eq!(val, temp);
}
pub mod stringify {
use super::*;
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.collect_str(value)
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
pub mod hexify_bytes {
use super::*;
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display + std::fmt::LowerHex,
S: Serializer,
{
serializer.serialize_str(&format!("{value:#x}"))
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
pub mod hexify_vec_bytes {
use super::*;
use std::borrow::Cow;
pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = vec![0; 2 + value.len() * 2];
s.get_mut(0..2)
.expect("len is correct")
.copy_from_slice(b"0x");
hex::encode_to_slice(value, s.get_mut(2..).expect("len is correct"))
.map_err(serde::ser::Error::custom)?;
serializer.serialize_str(std::str::from_utf8(&s).expect("valid utf8"))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let s = Cow::from(s.strip_prefix("0x").unwrap_or(&s));
let s = if s.len() % 2 == 0 {
s
} else {
let mut s = s.into_owned();
s.insert(0, '0');
Cow::Owned(s)
};
hex::decode(s.as_ref()).map_err(serde::de::Error::custom)
}
}
pub mod hexify {
use super::*;
use num_traits::Num;
use serde::{Deserializer, Serializer};
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Num + std::fmt::LowerHex,
S: Serializer,
{
serializer.serialize_str(format!("{value:#x}").as_str())
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Num,
<T as Num>::FromStrRadixErr: std::fmt::Display,
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
#[allow(clippy::indexing_slicing)]
if s.len() > 2 && &s[..2] == "0x" {
T::from_str_radix(&s[2..], 16).map_err(serde::de::Error::custom)
} else {
Err(serde::de::Error::custom("Invalid hex"))
}
}
}
pub mod base64_standard {
use super::*;
use base64::engine::{Engine as _, general_purpose::STANDARD};
pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
STANDARD.encode(value).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
STANDARD
.decode(String::deserialize(deserializer)?)
.map_err(serde::de::Error::custom)
}
}
pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: HasLotusJson + Clone,
{
value.clone().into_lotus_json().serialize(serializer)
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: HasLotusJson,
{
Ok(T::from_lotus_json(Deserialize::deserialize(deserializer)?))
}
#[derive(
Debug, Deserialize, From, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Clone,
)]
#[serde(bound = "T: HasLotusJson + Clone", transparent)]
pub struct LotusJson<T>(#[serde(with = "self")] pub T);
impl<T> JsonSchema for LotusJson<T>
where
T: HasLotusJson,
T::LotusJson: JsonSchema,
{
fn schema_name() -> std::borrow::Cow<'static, str> {
T::LotusJson::schema_name()
}
fn schema_id() -> std::borrow::Cow<'static, str> {
T::LotusJson::schema_id()
}
fn json_schema(g: &mut SchemaGenerator) -> Schema {
T::LotusJson::json_schema(g)
}
}
impl<T> LotusJson<T> {
pub fn into_inner(self) -> T {
self.0
}
}
macro_rules! lotus_json_with_self {
($($domain_ty:ty),* $(,)?) => {
$(
impl $crate::lotus_json::HasLotusJson for $domain_ty {
type LotusJson = Self;
#[cfg(test)]
fn snapshots() -> Vec<(serde_json::Value, Self)> {
unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
}
fn into_lotus_json(self) -> Self::LotusJson {
self
}
fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
lotus_json
}
}
)*
}
}
pub(crate) use lotus_json_with_self;
lotus_json_with_self!(
u32,
u64,
i64,
f64,
String,
chrono::DateTime<chrono::Utc>,
serde_json::Value,
(),
std::path::PathBuf,
bool,
DeadlineInfo,
PaddedPieceSize,
Uuid,
std::num::NonZeroUsize,
);
mod fixme {
use super::*;
impl<T: HasLotusJson> HasLotusJson for (T,) {
type LotusJson = (T::LotusJson,);
#[cfg(test)]
fn snapshots() -> Vec<(serde_json::Value, Self)> {
unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
}
fn into_lotus_json(self) -> Self::LotusJson {
(self.0.into_lotus_json(),)
}
fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
(HasLotusJson::from_lotus_json(lotus_json.0),)
}
}
impl<A: HasLotusJson, B: HasLotusJson> HasLotusJson for (A, B) {
type LotusJson = (A::LotusJson, B::LotusJson);
#[cfg(test)]
fn snapshots() -> Vec<(serde_json::Value, Self)> {
unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
}
fn into_lotus_json(self) -> Self::LotusJson {
(self.0.into_lotus_json(), self.1.into_lotus_json())
}
fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
(
HasLotusJson::from_lotus_json(lotus_json.0),
HasLotusJson::from_lotus_json(lotus_json.1),
)
}
}
impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson> HasLotusJson for (A, B, C) {
type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson);
#[cfg(test)]
fn snapshots() -> Vec<(serde_json::Value, Self)> {
unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
}
fn into_lotus_json(self) -> Self::LotusJson {
(
self.0.into_lotus_json(),
self.1.into_lotus_json(),
self.2.into_lotus_json(),
)
}
fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
(
HasLotusJson::from_lotus_json(lotus_json.0),
HasLotusJson::from_lotus_json(lotus_json.1),
HasLotusJson::from_lotus_json(lotus_json.2),
)
}
}
impl<A: HasLotusJson, B: HasLotusJson, C: HasLotusJson, D: HasLotusJson> HasLotusJson
for (A, B, C, D)
{
type LotusJson = (A::LotusJson, B::LotusJson, C::LotusJson, D::LotusJson);
#[cfg(test)]
fn snapshots() -> Vec<(serde_json::Value, Self)> {
unimplemented!("tests are trivial for HasLotusJson<LotusJson = Self>")
}
fn into_lotus_json(self) -> Self::LotusJson {
(
self.0.into_lotus_json(),
self.1.into_lotus_json(),
self.2.into_lotus_json(),
self.3.into_lotus_json(),
)
}
fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
(
HasLotusJson::from_lotus_json(lotus_json.0),
HasLotusJson::from_lotus_json(lotus_json.1),
HasLotusJson::from_lotus_json(lotus_json.2),
HasLotusJson::from_lotus_json(lotus_json.3),
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ipld_core::serde::SerdeError;
use serde::de::{IntoDeserializer, value::StringDeserializer};
#[derive(Debug, Deserialize, Serialize, PartialEq)]
struct HexifyVecBytesTest {
#[serde(with = "hexify_vec_bytes")]
value: Vec<u8>,
}
#[test]
fn test_hexify_vec_bytes_serialize() {
let cases = [(vec![], "0x"), (vec![0], "0x00"), (vec![42, 66], "0x2a42")];
for (input, expected) in cases.into_iter() {
let hexify = HexifyVecBytesTest { value: input };
let serialized = serde_json::to_string(&hexify).unwrap();
self::assert_eq!(serialized, format!("{{\"value\":\"{}\"}}", expected));
}
}
#[test]
fn test_hexify_vec_bytes_deserialize() {
let cases = [
("0x", vec![]),
("0x0", vec![0]),
("0xF", vec![15]),
("0x2a42", vec![42, 66]),
("0x2A42", vec![42, 66]),
];
for (input, expected) in cases.into_iter() {
let deserializer: StringDeserializer<SerdeError> =
String::from_str(input).unwrap().into_deserializer();
let deserialized = hexify_vec_bytes::deserialize(deserializer).unwrap();
self::assert_eq!(deserialized, expected);
}
let fail_cases = ["cthulhu", "x", "0xazathoth"];
for input in fail_cases.into_iter() {
let deserializer: StringDeserializer<SerdeError> =
String::from_str(input).unwrap().into_deserializer();
let deserialized = hexify_vec_bytes::deserialize(deserializer);
assert!(deserialized.is_err());
}
}
}