trs-data-value 0.2.0

Data Value for common data types
Documentation
use std::collections::HashMap;

#[cfg(feature = "python")]
mod python;

mod basic_operation;
mod deserializer;
mod extract;
mod from;
mod operators;
mod utils;
pub use smartstring::alias::String;

/// Represents a generic value (something like serde_json::Value) which can be
/// converted from/to python and provides other benefits.
#[derive(Debug, Clone, Default)]
pub enum DataValue {
    String(String),
    Bytes(Vec<u8>),
    U8(u8),
    Bool(bool),
    I32(i32),
    U32(u32),
    I64(i64),
    U64(u64),
    I128(i128),
    U128(u128),
    F32(f32),
    F64(f64),
    Map(HashMap<String, DataValue>),
    Vec(Vec<DataValue>),
    EnumNumber(i32),
    #[default]
    Null,
}

impl std::fmt::Display for DataValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use DataValue::*;
        match self {
            Bool(i) => write!(f, "{}", i),
            U32(i) => write!(f, "{}", i),
            I32(i) => write!(f, "{}", i),
            U8(i) => write!(f, "{}", i),
            U64(i) => write!(f, "{}", i),
            I64(i) => write!(f, "{}", i),
            F32(i) => write!(f, "{}", i),
            F64(i) => write!(f, "{}", i),
            U128(i) => write!(f, "{}", i),
            I128(i) => write!(f, "{}", i),
            String(i) => write!(f, "{}", i),
            Bytes(i) => write!(f, "{}", std::string::String::from_utf8_lossy(i)),
            Null => write!(f, "null",),
            Vec(i) => write!(f, "{:?}", i),
            EnumNumber(i) => write!(f, "{:?}", i),
            Map(i) => write!(f, "{:?}", i),
        }
    }
}
impl DataValue {
    pub fn extract<T>(&self) -> T
    where
        T: Extract,
    {
        T::extract(self)
    }

    pub fn extract_bytes(&self) -> Vec<u8> {
        match self {
            DataValue::Bytes(i) => i.to_vec(),
            _ => vec![],
        }
    }
}

pub trait Extract {
    fn extract(v: &DataValue) -> Self;
}

impl DataValue {
    pub fn contains(&self, other: &str) -> bool {
        match self {
            DataValue::Vec(v) => v.iter().any(|x| x.contains(other)),
            DataValue::Map(v) => v.keys().any(|x| x.contains(other)),
            DataValue::String(v) => v.contains(other),
            _ => false,
        }
    }
}

pub fn convert<D: Extract>(data: &[DataValue]) -> Vec<D> {
    let mut result = Vec::with_capacity(data.len());
    for d in data {
        result.push(D::extract(d));
    }
    result
}

#[macro_export]
macro_rules! datavalue {

     // case vec![1, 2, 3]
    ($(vec![$($value:expr),*]),*) => {
        $crate::datavalue!($([$($value),*]),*)
    };

    // case vec![1, 2, 3]
    ($([$($value:expr),*]),*) => {
        {
        $(
            let mut _vec = Vec::new();
            $(
                _vec.push($crate::DataValue::from($value));
            )*
            $crate::DataValue::Vec(_vec)
        )*
        }
    };
    // case { "a" => 1, }
    ($($key:expr => $value:expr,)+) => { $crate::datavalue!($($key => $value),+) };
    // case { "a" => vec![1, 2, 3] }
    ($($key:expr => vec![$($value:expr),*]),*) => {
        $crate::datavalue!($($key => [$($value),*]),*)
    };
    // case { "a" => [1, 2, 3] }
    ($($key:expr => [$($value:expr),*]),*) => {
        {
            let mut _map = ::std::collections::HashMap::new();
            $(
                let mut _arr = Vec::new();
                $(
                    _arr.push($value.into());
                )*
                _map.insert($key.into(), $crate::DataValue::Vec(_arr));
            )*
            $crate::DataValue::Map(_map)
        }
    };
    // case { "a" => 1 }
    ($($key:expr => $value:expr),*) => {
        {
            let mut _map = ::std::collections::HashMap::new();
            $(
                _map.insert($key.into(), $value.into());
            )*
            $crate::DataValue::Map(_map)
        }
    };
    // case simple value
    ($value:expr) => {
        $crate::DataValue::from($value)
    };
}
#[cfg(test)]
mod test {
    use std::fmt::Debug;

    use super::*;
    use rstest::*;

    #[test]
    fn from_json_object_to_datavalue() {
        let value = DataValue::from(&serde_json::json!(12));
        assert_eq!(value, DataValue::I64(12));

        let value = DataValue::from(&serde_json::json!(12.0));
        assert_eq!(value, DataValue::F64(12.0));

        let value = DataValue::from(&serde_json::json!("12"));
        assert_eq!(value, DataValue::String("12".into()));

        let value = DataValue::from(&serde_json::json!(true));
        assert_eq!(value, DataValue::Bool(true));

        let value = DataValue::from(&serde_json::json!(false));
        assert_eq!(value, DataValue::Bool(false));

        let value = DataValue::from(&serde_json::json!(null));
        assert_eq!(value, DataValue::Null);
    }

    #[test]
    fn from_native_to_datavalue() {
        let value = DataValue::from(12u32);
        assert_eq!(value, DataValue::U32(12));

        let value = DataValue::from(12.0f64);
        assert_eq!(value, DataValue::F64(12.0));

        let value = DataValue::from("12");
        assert_eq!(value, DataValue::String("12".into()));

        let value = DataValue::from(true);
        assert_eq!(value, DataValue::Bool(true));

        let value = DataValue::from(false);
        assert_eq!(value, DataValue::Bool(false));

        let value = DataValue::from(());
        assert_eq!(value, DataValue::Null);
    }

    #[rstest]
    #[case(DataValue::U32(12), 12u32)]
    #[case(DataValue::I32(12), 12i32)]
    #[case(DataValue::U8(12), 12u8)]
    #[case(DataValue::U64(12), 12u64)]
    #[case(DataValue::I64(12), 12i64)]
    #[case(DataValue::F32(12.0), 12f32)]
    #[case(DataValue::F64(12.0), 12f64)]
    #[case(DataValue::U128(12), 12u128)]
    #[case(DataValue::I128(12), 12i128)]
    #[case(DataValue::String("12".into()), "12".to_string())]
    #[case(DataValue::Bool(true), true)]
    #[case(DataValue::Bool(false), false)]
    fn as_data_value_test<T: PartialEq + std::fmt::Debug + Into<DataValue> + Extract>(
        #[case] value: DataValue,
        #[case] expected: T,
    ) {
        assert_eq!(value.extract::<T>(), expected);
        assert_eq!(value, expected.into());
    }

    #[rstest]
    #[case(DataValue::Bytes("12".as_bytes().to_vec()), "12".as_bytes().to_vec())]
    fn as_data_value_test_bytes(#[case] value: DataValue, #[case] expected: Vec<u8>) {
        assert_eq!(value.extract_bytes(), expected);
    }

    #[rstest]
    #[case(DataValue::U32(12), DataValue::U32(12), true)]
    #[case(DataValue::I32(12), DataValue::I32(12), true)]
    #[case(DataValue::U8(12), DataValue::U8(12), true)]
    #[case(DataValue::U64(12), DataValue::U64(12), true)]
    #[case(DataValue::I64(12), DataValue::I64(12), true)]
    #[case(DataValue::F32(12.0), DataValue::F32(12.0), true)]
    #[case(DataValue::F64(12.0), DataValue::F64(12.0), true)]
    #[case(DataValue::U128(12), DataValue::U128(12), true)]
    #[case(DataValue::I128(12), DataValue::I128(12), true)]
    #[case(DataValue::String("12".into()), DataValue::String("12".into()), true)]
    #[case(DataValue::Bytes("12".as_bytes().to_vec()), DataValue::Bytes("12".as_bytes().to_vec()), true)]
    #[case(DataValue::Bool(true), DataValue::Bool(true), true)]
    #[case(DataValue::Bool(false), DataValue::Bool(false), true)]
    fn eq_test(#[case] value: DataValue, #[case] other: DataValue, #[case] expected: bool) {
        assert_eq!(value == other, expected);
        print!("{} == {} = {}", value, other, expected);
    }

    #[rstest]
    #[case(
        crate::datavalue!(1u32),
        DataValue::U32(1),
        vec![1u32]
    )]
    #[case(
        crate::datavalue!(vec![1u32, 2u32]),
        DataValue::Vec(vec![DataValue::U32(1), DataValue::U32(2)]),
        vec![vec![1u32, 2u32]]
    )]
    fn convert_test<T: Extract + PartialEq + Debug>(
        #[case] value: DataValue,
        #[case] expected: DataValue,
        #[case] expected_native: Vec<T>,
    ) {
        assert_eq!(value, expected);
        let converted: Vec<T> = convert::<T>(&[value]);
        assert_eq!(converted, expected_native);
    }
}