vertigo 0.11.4

Reactive Real-DOM library with SSR for Rust
Documentation
use std::collections::{BTreeMap, HashMap};

use crate::driver_module::js_value::MapItem;
use crate::driver_module::js_value::js_json_struct::JsJsonNumber;

use super::JsJsonContext;
use super::js_json_struct::JsJson;

pub trait JsJsonSerialize {
    fn to_json(self) -> JsJson;
}

pub trait JsJsonDeserialize
where
    Self: Sized,
{
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext>;
}

impl JsJsonSerialize for String {
    fn to_json(self) -> JsJson {
        JsJson::String(self)
    }
}

impl JsJsonDeserialize for String {
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
        match json {
            JsJson::String(value) => Ok(value),
            other => {
                let message = ["string expected, received ", other.typename()].concat();
                Err(context.add(message))
            }
        }
    }
}

macro_rules! impl_js_json_for_number {
    ($name:ty) => {
        impl JsJsonSerialize for $name {
            fn to_json(self) -> JsJson {
                JsJson::Number(JsJsonNumber(self as f64))
            }
        }

        impl JsJsonDeserialize for $name {
            fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
                match json {
                    JsJson::Number(JsJsonNumber(value)) => Ok(value as Self),
                    other => Err(context
                        .add(["number($name) expected, received ", other.typename()].concat())),
                }
            }
        }
    };
}

impl_js_json_for_number!(i8);
impl_js_json_for_number!(i16);
impl_js_json_for_number!(i32);
impl_js_json_for_number!(i64);
impl_js_json_for_number!(isize);

impl_js_json_for_number!(u8);
impl_js_json_for_number!(u16);
impl_js_json_for_number!(u32);
impl_js_json_for_number!(u64);
impl_js_json_for_number!(usize);

impl_js_json_for_number!(f32);
impl_js_json_for_number!(f64);

impl JsJsonSerialize for bool {
    fn to_json(self) -> JsJson {
        match self {
            false => JsJson::False,
            true => JsJson::True,
        }
    }
}

impl JsJsonDeserialize for bool {
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
        match json {
            JsJson::False => Ok(false),
            JsJson::True => Ok(true),
            other => {
                let message = ["bool expected, received ", other.typename()].concat();
                Err(context.add(message))
            }
        }
    }
}

impl JsJsonSerialize for () {
    fn to_json(self) -> JsJson {
        JsJson::Object(BTreeMap::default())
    }
}

impl JsJsonDeserialize for () {
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
        let map = json.get_hashmap(&context)?;

        if !map.is_empty() {
            let message = "Empty {} expected, inner content received".to_string();
            return Err(context.add(message));
        }

        Ok(())
    }
}

impl JsJsonSerialize for &str {
    fn to_json(self) -> JsJson {
        JsJson::String(self.into())
    }
}

impl<T: JsJsonSerialize> JsJsonSerialize for Vec<T> {
    fn to_json(self) -> JsJson {
        let mut list = Vec::new();

        for item in self {
            list.push(item.to_json());
        }

        JsJson::List(list)
    }
}

impl<T: JsJsonDeserialize> JsJsonDeserialize for Vec<T> {
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
        let mut list = Vec::new();

        let JsJson::List(inner) = json else {
            let message = ["List expected, received ", json.typename()].concat();
            return Err(context.add(message));
        };

        for (index, item) in inner.into_iter().enumerate() {
            list.push(T::from_json(context.add(index), item)?);
        }

        Ok(list)
    }
}

impl<T: JsJsonSerialize> JsJsonSerialize for Option<T> {
    fn to_json(self) -> JsJson {
        match self {
            Some(value) => value.to_json(),
            None => JsJson::Null,
        }
    }
}

impl<T: JsJsonDeserialize> JsJsonDeserialize for Option<T> {
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
        if let JsJson::Null = json {
            return Ok(None);
        }

        Ok(Some(T::from_json(context, json)?))
    }
}

impl<T: JsJsonSerialize> JsJsonSerialize for HashMap<String, T> {
    fn to_json(self) -> JsJson {
        let mut result = BTreeMap::new();

        for (key, item) in self {
            result.insert(key, item.to_json());
        }

        JsJson::Object(result)
    }
}

impl<T: JsJsonDeserialize> JsJsonDeserialize for HashMap<String, T> {
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
        let map = json.get_hashmap(&context)?;

        let mut result = HashMap::new();

        for (key, item) in map {
            let context = context.add(["field: '", key.as_str(), "'"].concat());
            let value = T::from_json(context, item)?;
            result.insert(key, value);
        }

        Ok(result)
    }
}

impl<K: JsJsonSerialize + JsJsonDeserialize, T: JsJsonSerialize + JsJsonDeserialize> JsJsonSerialize
    for BTreeMap<K, T>
{
    fn to_json(self) -> JsJson {
        let mut result = Vec::<JsJson>::new();

        for (key, item) in self {
            result.push(MapItem { key, value: item }.to_json());
        }

        JsJson::List(result)
    }
}

impl<K: JsJsonSerialize + JsJsonDeserialize + Ord, T: JsJsonSerialize + JsJsonDeserialize>
    JsJsonDeserialize for BTreeMap<K, T>
{
    fn from_json(context: JsJsonContext, json: JsJson) -> Result<Self, JsJsonContext> {
        let JsJson::List(list) = json else {
            let message = ["list expected, received ", json.typename()].concat();
            return Err(context.add(message));
        };

        let mut result = BTreeMap::new();

        for (index, item) in list.into_iter().enumerate() {
            let item = from_json::<MapItem<K, T>>(item)
                .map_err(|err| context.add(format!("index={index} error={err}")))?;

            let exist = result.insert(item.key, item.value);

            if exist.is_some() {
                return Err(context.add("Duplicate key"));
            }
        }

        Ok(result)
    }
}

/// Deserialize from JsJson to T
pub fn from_json<T: JsJsonDeserialize>(json: JsJson) -> Result<T, String> {
    let context = JsJsonContext::new("root");
    let result = T::from_json(context, json);
    result.map_err(|context| context.convert_to_string())
}

/// Serialize T to JsJson
pub fn to_json<T: JsJsonSerialize>(value: T) -> JsJson {
    value.to_json()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, PartialEq, Clone)]
    struct Post {
        name: String,
        age: u64,
    }

    impl JsJsonSerialize for Post {
        fn to_json(self) -> JsJson {
            JsJson::Object(BTreeMap::from([
                ("name".to_string(), self.name.to_json()),
                ("age".to_string(), self.age.to_json()),
            ]))
        }
    }

    impl JsJsonDeserialize for Post {
        fn from_json(context: JsJsonContext, mut json: JsJson) -> Result<Self, JsJsonContext> {
            Ok(Self {
                name: json.get_property(&context, "name")?,
                age: json.get_property(&context, "age")?,
            })
        }
    }

    #[test]
    fn aaaa() {
        let aaa = JsJson::String("aaa".into());
        let aaa_post = from_json::<Post>(aaa);
        assert_eq!(
            aaa_post,
            Err(String::from("root -> object expected, received string"))
        );

        let bbb = Post {
            name: "dsada".into(),
            age: 33,
        };

        let ccc = bbb.clone().to_json();
        let Ok(ddd) = from_json::<Post>(ccc) else {
            unreachable!();
        };
        assert_eq!(bbb, ddd);
    }

    #[test]
    fn test_vec() {
        let aaa = Post {
            name: "aaa".into(),
            age: 11,
        };

        let bbb = Post {
            name: "bbb".into(),
            age: 22,
        };

        let ccc = vec![aaa, bbb];

        let ddd = ccc.clone().to_json();

        let Ok(eee) = from_json::<Vec<Post>>(ddd) else {
            unreachable!();
        };

        assert_eq!(ccc, eee);
    }

    #[test]
    fn test_unit() {
        let unit = JsJson::Object(BTreeMap::default());

        let Ok(()) = from_json::<()>(unit.clone()) else {
            unreachable!();
        };

        let unit2 = to_json(());

        assert_eq!(unit2, unit)
    }

    #[test]
    fn test_serialize_to_string() {
        let data: Vec<u8> = b"Hello, world!".to_vec();

        let encoded = data
            .iter()
            .map(|b| format!("{:02x}", b))
            .collect::<String>();

        let decoded = (0..encoded.len())
            .step_by(2)
            .filter_map(|i| {
                let decoded_i = u8::from_str_radix(&encoded[i..i + 2], 16);
                assert!(decoded_i.is_ok());
                decoded_i.ok()
            })
            .collect::<Vec<u8>>();

        println!("Original: {}", String::from_utf8_lossy(&decoded));
    }
}