use serde::{Deserialize, Serialize};
pub trait State: Serialize {
type Target: Serialize + for<'de> Deserialize<'de>;
}
macro_rules! primitive_impl {
($($t:ty),*) => {
$(
impl State for $t {
type Target = Self;
}
)*
};
}
primitive_impl!(
bool, char, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);
impl State for String {
type Target = Self;
}
impl<T: State> State for Option<T> {
type Target = Option<T::Target>;
}
impl<T: State> State for Vec<T> {
type Target = Vec<T::Target>;
}
impl<T: State, E: State> State for Result<T, E> {
type Target = Result<T::Target, E::Target>;
}
impl<T: State> State for Box<T> {
type Target = Box<T::Target>;
}
impl<K: State, V: State> State for std::collections::HashMap<K, V>
where
K::Target: std::hash::Hash + Eq,
{
type Target = std::collections::HashMap<K::Target, V::Target>;
}
impl<K: State, V: State> State for std::collections::BTreeMap<K, V>
where
K::Target: Ord,
{
type Target = std::collections::BTreeMap<K::Target, V::Target>;
}
macro_rules! tuple_impl {
() => {
impl State for () {
type Target = ();
}
};
($($name:ident)+) => {
impl<$($name: State),+> State for ($($name,)+) {
type Target = ($($name::Target,)+);
}
};
}
tuple_impl!();
tuple_impl!(T0);
tuple_impl!(T0 T1);
tuple_impl!(T0 T1 T2);
tuple_impl!(T0 T1 T2 T3);
tuple_impl!(T0 T1 T2 T3 T4);
tuple_impl!(T0 T1 T2 T3 T4 T5);
tuple_impl!(T0 T1 T2 T3 T4 T5 T6);
tuple_impl!(T0 T1 T2 T3 T4 T5 T6 T7);
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct TestModel {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub optional_field: Option<String>,
pub internal_field: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct TargetTestModel {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub optional_field: Option<String>,
}
impl State for TestModel {
type Target = TargetTestModel;
}
fn to_target<T: State>(t: &T) -> Result<T::Target, serde_json::Error> {
let value = serde_json::to_value(t)?;
let target = serde_json::from_value(value)?;
Ok(target)
}
#[test]
fn test_model_conversion() {
let model = TestModel {
name: "test".to_string(),
optional_field: Some("value".to_string()),
internal_field: Some("internal".to_string()),
};
let target = to_target(&model).unwrap();
assert_eq!(target.name, "test");
assert_eq!(target.optional_field, Some("value".to_string()));
}
#[test]
fn test_serialization_compatibility() {
let model = TestModel {
name: "test".to_string(),
optional_field: None,
internal_field: Some("internal".to_string()),
};
let target = to_target(&model).unwrap();
let model_json = serde_json::to_value(&model).unwrap();
let target_json = serde_json::to_value(&target).unwrap();
let mut model_json_comparable = model_json.as_object().unwrap().clone();
model_json_comparable.remove("internal_field");
assert_eq!(
serde_json::Value::Object(model_json_comparable),
target_json
);
}
}