uninode 0.4.2

Universal object type
Documentation
//
// Copyright (c) 2022 Oleg Lelenkov <o.lelenkov@gmail.com>
// Distributed under terms of the BSD 3-Clause license.
//

use std::collections::HashMap;
use std::fmt::{self, Display};

use super::error::{Result, UniNodeError, Expected};

pub type Map<K, V> = HashMap<K, V>;

/// Type representing a UniNode array, payload of the `UniNode::Array` variant
pub type Array = Vec<UniNode>;

/// Type representing a UniNode object, payload of the `UniNode::Object` variant.
pub type Object = Map<String, UniNode>;

/// Representation of a UniNode value.
#[derive(Debug, Clone, PartialEq)]
pub enum UniNode {
    /// Represents a UniNode null
    Null,
    /// Represents a UniNode bool
    Boolean(bool),
    /// Represents a UniNode integer
    Integer(i64),
    /// Represents a UniNode unsigned integer
    UInteger(u64),
    /// Represents a UniNode float
    Float(f64),
    /// Represents a UniNode bytes array
    Bytes(Vec<u8>),
    /// Represents a UniNode string
    String(String),
    /// Represents a UniNode array
    Array(Array),
    /// Represents a UniNode object
    Object(Object),
}

macro_rules! impl_is_function {
    ($name:ident, $member:ident) => {
        pub fn $name(&self) -> bool {
            match self {
                UniNode::$member(_) => true,
                _ => false,
            }
        }
    };
}

impl UniNode {
    impl_is_function!(is_bool, Boolean);

    impl_is_function!(is_int, Integer);

    impl_is_function!(is_uint, UInteger);

    impl_is_function!(is_float, Float);

    impl_is_function!(is_bytes, Bytes);

    impl_is_function!(is_string, String);

    impl_is_function!(is_object, Object);

    impl_is_function!(is_array, Array);

    pub fn empty_array() -> Self {
        UniNode::Array(Array::new())
    }

    pub fn empty_object() -> Self {
        UniNode::Object(Object::new())
    }

    pub fn is_null(&self) -> bool {
        matches!(self, UniNode::Null)
    }

    pub fn is_some_int(&self) -> bool {
        matches!(self, UniNode::Integer(_) | UniNode::UInteger(_))
    }

    fn invalid_error(&self, expected: Expected) -> UniNodeError {
        UniNodeError::invalid_type(self, expected)
    }

    pub fn as_bool(&self) -> Result<bool> {
        match self {
            UniNode::Boolean(val) => Ok(*val),
            UniNode::Integer(val) => Ok(*val != 0),
            UniNode::UInteger(val) => Ok(*val != 0),
            UniNode::Float(val) => Ok(*val != 0.0),
            UniNode::String(val) => match val.to_lowercase().as_ref() {
                "1" | "true" | "on" | "yes" => Ok(true),
                "0" | "false" | "off" | "no" => Ok(false),
                _ => Err(self.invalid_error(Expected::Bool)),
            },
            _ => Err(self.invalid_error(Expected::Bool)),
        }
    }

    pub fn as_int(&self) -> Result<i64> {
        match self {
            UniNode::Boolean(val) => Ok(if *val { 1 } else { 0 }),
            UniNode::Integer(val) => Ok(*val),
            UniNode::UInteger(val) => i64::try_from(*val)
                .map_err(|_| self.invalid_error(Expected::Integer)),
            UniNode::Float(val) => Ok(val.round() as i64),
            UniNode::String(val) => match val.to_lowercase().as_ref() {
                "1" | "true" | "on" | "yes" => Ok(1),
                "0" | "false" | "off" | "no" => Ok(0),
                _ => val
                    .parse()
                    .map_err(|_| self.invalid_error(Expected::Integer)),
            },
            _ => Err(self.invalid_error(Expected::Integer)),
        }
    }

    pub fn as_uint(&self) -> Result<u64> {
        match self {
            UniNode::Boolean(val) => Ok(if *val { 1 } else { 0 }),
            UniNode::UInteger(val) => Ok(*val),
            UniNode::Integer(val) => u64::try_from(*val)
                .map_err(|_| self.invalid_error(Expected::UInteger)),
            UniNode::Float(val) => u64::try_from(val.round() as i64)
                .map_err(|_| self.invalid_error(Expected::UInteger)),
            UniNode::String(val) => match val.to_lowercase().as_ref() {
                "1" | "true" | "on" | "yes" => Ok(1),
                "0" | "false" | "off" | "no" => Ok(0),
                _ => val
                    .parse()
                    .map_err(|_| self.invalid_error(Expected::UInteger)),
            },
            _ => Err(self.invalid_error(Expected::UInteger)),
        }
    }

    pub fn as_float(&self) -> Result<f64> {
        match self {
            UniNode::Boolean(val) => Ok(if *val { 1.0 } else { 0.0 }),
            UniNode::Integer(val) => Ok(*val as f64),
            UniNode::UInteger(val) => Ok(*val as f64),
            UniNode::Float(val) => Ok(*val),
            UniNode::String(val) => match val.to_lowercase().as_ref() {
                "1" | "true" | "on" | "yes" => Ok(1.0),
                "0" | "false" | "off" | "no" => Ok(0.0),
                _ => {
                    val.parse().map_err(|_| self.invalid_error(Expected::Float))
                },
            },
            _ => Err(self.invalid_error(Expected::Float)),
        }
    }

    pub fn as_str(&self) -> Result<&str> {
        match self {
            UniNode::String(val) => Ok(val),
            _ => Err(self.invalid_error(Expected::String)),
        }
    }

    pub fn as_bytes(&self) -> Result<&[u8]> {
        match self {
            UniNode::Bytes(val) => Ok(val),
            UniNode::String(val) => Ok(val.as_bytes()),
            _ => Err(self.invalid_error(Expected::Bytes)),
        }
    }

    pub fn to_bytes(self) -> Result<Vec<u8>> {
        match self {
            UniNode::Bytes(val) => Ok(val),
            UniNode::String(val) => Ok(val.into_bytes()),
            _ => Err(self.invalid_error(Expected::Bytes)),
        }
    }

    pub fn to_string(self) -> Result<String> {
        match self {
            UniNode::String(val) => Ok(val),
            UniNode::Boolean(val) => Ok(val.to_string()),
            UniNode::Integer(val) => Ok(val.to_string()),
            UniNode::UInteger(val) => Ok(val.to_string()),
            UniNode::Float(val) => Ok(val.to_string()),
            _ => Err(self.invalid_error(Expected::String)),
        }
    }

    pub fn as_array(&self) -> Result<&Vec<UniNode>> {
        match self {
            UniNode::Array(val) => Ok(val),
            _ => Err(self.invalid_error(Expected::Array)),
        }
    }

    pub fn as_array_mut(&mut self) -> Result<&mut Vec<UniNode>> {
        match self {
            UniNode::Array(ref mut val) => Ok(val),
            _ => Err(self.invalid_error(Expected::Array)),
        }
    }

    pub fn to_array(self) -> Result<Array> {
        match self {
            UniNode::Array(val) => Ok(val),
            _ => Err(self.invalid_error(Expected::Array)),
        }
    }

    pub fn as_object(&self) -> Result<&Map<String, UniNode>> {
        match self {
            UniNode::Object(val) => Ok(val),
            _ => Err(self.invalid_error(Expected::Object)),
        }
    }

    pub fn as_object_mut(&mut self) -> Result<&mut Map<String, UniNode>> {
        match self {
            UniNode::Object(ref mut val) => Ok(val),
            _ => Err(self.invalid_error(Expected::Object)),
        }
    }

    pub fn to_object(self) -> Result<Object> {
        match self {
            UniNode::Object(val) => Ok(val),
            _ => Err(self.invalid_error(Expected::Object)),
        }
    }

    pub fn into_array(self) -> UniNode {
        match self {
            UniNode::Array(_) => self,
            _ => UniNode::Array(vec![self]),
        }
    }
}

mod inner {
    use super::{Map, UniNode};

    pub trait InnerType {}

    macro_rules! impl_value_into {
        ($($values:ty),+ $(,)?) => {
            $(impl InnerType for $values {})+
        }
    }

    impl_value_into!(
        bool, i8, i16, i32, i64, u16, u32, u64, f32, f64, String, &str
    );

    impl<T: Into<UniNode>> InnerType for Option<T> {}

    impl<T: Into<UniNode>> InnerType for Vec<T> {}

    impl<T: Into<UniNode>> InnerType for Map<String, T> {}
}

impl From<bool> for UniNode {
    fn from(value: bool) -> Self {
        UniNode::Boolean(value)
    }
}

impl From<i8> for UniNode {
    fn from(value: i8) -> Self {
        UniNode::Integer(value as i64)
    }
}

impl From<i16> for UniNode {
    fn from(value: i16) -> Self {
        UniNode::Integer(value as i64)
    }
}

impl From<i32> for UniNode {
    fn from(value: i32) -> Self {
        UniNode::Integer(value as i64)
    }
}

impl From<i64> for UniNode {
    fn from(value: i64) -> Self {
        UniNode::Integer(value)
    }
}

impl From<u8> for UniNode {
    fn from(value: u8) -> Self {
        UniNode::UInteger(value as u64)
    }
}

impl From<u16> for UniNode {
    fn from(value: u16) -> Self {
        UniNode::UInteger(value as u64)
    }
}

impl From<u32> for UniNode {
    fn from(value: u32) -> Self {
        UniNode::UInteger(value as u64)
    }
}

impl From<u64> for UniNode {
    fn from(value: u64) -> Self {
        UniNode::UInteger(value)
    }
}

impl From<f32> for UniNode {
    fn from(value: f32) -> Self {
        UniNode::Float(value as f64)
    }
}

impl From<f64> for UniNode {
    fn from(value: f64) -> Self {
        UniNode::Float(value)
    }
}

impl From<String> for UniNode {
    fn from(value: String) -> Self {
        UniNode::String(value)
    }
}

impl From<&str> for UniNode {
    fn from(value: &str) -> Self {
        UniNode::String(value.into())
    }
}

impl<T> From<Option<T>> for UniNode
where
    T: Into<UniNode>,
{
    fn from(value: Option<T>) -> Self {
        match value {
            Some(value) => value.into(),
            None => UniNode::Null,
        }
    }
}

impl From<Vec<u8>> for UniNode {
    fn from(value: Vec<u8>) -> Self {
        UniNode::Bytes(value)
    }
}

impl From<&[u8]> for UniNode {
    fn from(value: &[u8]) -> Self {
        UniNode::Bytes(value.to_vec())
    }
}

impl<T> From<Vec<T>> for UniNode
where
    T: Into<UniNode> + inner::InnerType,
{
    fn from(value: Vec<T>) -> Self {
        let mut data = Vec::new();
        for v in value {
            data.push(v.into());
        }
        UniNode::Array(data)
    }
}

impl<T> From<Map<String, T>> for UniNode
where
    T: Into<UniNode>,
{
    fn from(value: Map<String, T>) -> Self {
        let mut data = Map::new();
        for (k, v) in value {
            let _ = data.insert(k.clone(), v.into());
        }
        UniNode::Object(data)
    }
}

impl Default for UniNode {
    fn default() -> Self {
        UniNode::Null
    }
}

impl Display for UniNode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            UniNode::Null => write!(f, "null"),
            UniNode::Boolean(val) => write!(f, "{}", val),
            UniNode::Integer(val) => write!(f, "{}", val),
            UniNode::UInteger(val) => write!(f, "{}", val),
            UniNode::Float(val) => write!(f, "{}", val),
            UniNode::String(val) => write!(f, "{}", val),
            UniNode::Bytes(val) => write!(f, "{:?}", val),
            UniNode::Array(val) => {
                write!(f, "[")?;
                for (i, item) in val.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}", item)?;
                }
                write!(f, "]")
            },
            UniNode::Object(val) => {
                write!(f, "{{")?;
                for (i, item) in val.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}: {}", item.0, item.1)?;
                }
                write!(f, "}}")
            },
        }
    }
}

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

    #[test]
    fn constructs() {
        assert_eq!(unode!(), UniNode::Null);
        assert_eq!(unode!(true), UniNode::Boolean(true));
        assert_eq!(unode!(5u8), UniNode::UInteger(5));
        assert_eq!(unode!("one"), UniNode::String("one".to_string()));
        assert_eq!(
            unode!(1, 2.4, "one", false),
            UniNode::Array(vec![
                UniNode::Integer(1),
                UniNode::Float(2.4),
                UniNode::String("one".to_string()),
                UniNode::Boolean(false),
            ])
        );
        assert_eq!(UniNode::from(Some(unode!(11))), unode!(11));
        assert_eq!(unode! {"one" => 1.0}, {
            let mut hm = HashMap::new();
            hm.insert("one".to_string(), UniNode::Float(1.0));
            UniNode::Object(hm)
        });
        assert_eq!(unode!(1, 2, 3), UniNode::from(vec![1, 2, 3]));
        assert_eq!(
            unode! {
                "one" => 1,
                "two" => 2
            },
            {
                let mut hm = HashMap::new();
                hm.insert("one".to_string(), UniNode::Integer(1));
                hm.insert("two".to_string(), UniNode::Integer(2));
                UniNode::Object(hm)
            }
        );
    }

    #[test]
    fn default() {
        assert_eq!(UniNode::default(), UniNode::Null);
    }

    #[test]
    fn is_functions() {
        assert!(UniNode::Null.is_null());
        assert!(UniNode::Boolean(false).is_bool());
        assert!(UniNode::Integer(1).is_int());
        assert!(UniNode::UInteger(1).is_uint());
        assert!(UniNode::Float(1.1).is_float());
        assert!(UniNode::String("".to_string()).is_string());
        assert!(UniNode::Bytes(vec![]).is_bytes());
        assert!(UniNode::empty_array().is_array());
        assert!(UniNode::empty_object().is_object());
        assert!(UniNode::Integer(1).is_some_int());
        assert!(UniNode::UInteger(1).is_some_int());
    }

    #[test]
    fn as_boolean() {
        fn bool_test(val: UniNode, s: bool) {
            assert_eq!(val.as_bool().unwrap(), s);
        }
        bool_test(unode!(true), true);
        bool_test(unode!(false), false);
        bool_test(unode!(1), true);
        bool_test(unode!(0), false);
        bool_test(unode!(1u8), true);
        bool_test(unode!(1.1), true);
        bool_test(unode!(0.0), false);
        bool_test(unode!("1"), true);
        bool_test(unode!("true"), true);
        bool_test(unode!("on"), true);
        bool_test(unode!("yes"), true);
        bool_test(unode!("0"), false);
        bool_test(unode!("false"), false);
        bool_test(unode!("off"), false);
        bool_test(unode!("no"), false);

        assert_eq!(
            format!("{}", unode!().as_bool().unwrap_err()),
            "invalid type: null value, expected a boolean"
        );
        assert_eq!(
            format!("{}", unode!("maybe").as_bool().unwrap_err()),
            "invalid type: string \"maybe\", expected a boolean"
        );
        assert_eq!(
            format!("{}", unode!(1, 2).as_bool().unwrap_err()),
            "invalid type: array, expected a boolean"
        );
        assert_eq!(
            format!("{}", unode!("one" => 1).as_bool().unwrap_err()),
            "invalid type: object, expected a boolean"
        );
    }

    #[test]
    fn as_integer() {
        fn integer_test(value: UniNode, val: i64) {
            assert_eq!(value.as_int().unwrap(), val);
        }
        integer_test(unode!(true), 1);
        integer_test(unode!(false), 0);
        integer_test(unode!(4), 4);
        integer_test(unode!(5u8), 5);
        assert_eq!(
        format!("{}", unode!(u64::MAX).as_int().unwrap_err()),
        "invalid type: unsigned integer `18446744073709551615`, expected a \
         integer"
    );
        integer_test(unode!(1.1), 1);
        integer_test(unode!(1.6), 2);
        integer_test(unode!(-1.6), -2);
        integer_test(unode!("1"), 1);
        integer_test(unode!("true"), 1);
        integer_test(unode!("on"), 1);
        integer_test(unode!("yes"), 1);
        integer_test(unode!("0"), 0);
        integer_test(unode!("false"), 0);
        integer_test(unode!("off"), 0);
        integer_test(unode!("no"), 0);
        assert_eq!(
            format!("{}", unode!().as_int().unwrap_err()),
            "invalid type: null value, expected a integer"
        );
        assert_eq!(
            format!("{}", unode!("one").as_int().unwrap_err()),
            "invalid type: string \"one\", expected a integer"
        );
        assert_eq!(
            format!("{}", unode!(1, 2).as_int().unwrap_err()),
            "invalid type: array, expected a integer"
        );
        assert_eq!(
            format!("{}", unode!("one" => 1).as_int().unwrap_err()),
            "invalid type: object, expected a integer"
        );
    }

    #[test]
    fn as_uinteger() {
        fn integer_test(value: UniNode, val: u64) {
            assert_eq!(value.as_uint().unwrap(), val);
        }
        integer_test(unode!(true), 1);
        integer_test(unode!(false), 0);
        integer_test(unode!(4i32), 4);
        integer_test(unode!(5u8), 5);
        assert_eq!(
        format!("{}", unode!(i64::MIN).as_uint().unwrap_err()),
        "invalid type: integer `-9223372036854775808`, expected a unsigned \
         integer"
    );
        integer_test(unode!(1.1), 1);
        assert_eq!(
            format!("{}", unode!(-2.6).as_uint().unwrap_err()),
            "invalid type: floating point `-2.6`, expected a unsigned integer"
        );

        integer_test(unode!("1"), 1);
        integer_test(unode!("true"), 1);
        integer_test(unode!("on"), 1);
        integer_test(unode!("yes"), 1);
        integer_test(unode!("0"), 0);
        integer_test(unode!("false"), 0);
        integer_test(unode!("off"), 0);
        integer_test(unode!("no"), 0);

        assert_eq!(
            format!("{}", unode!().as_uint().unwrap_err()),
            "invalid type: null value, expected a unsigned integer"
        );
        assert_eq!(
            format!("{}", unode!("one").as_uint().unwrap_err()),
            "invalid type: string \"one\", expected a unsigned integer"
        );
        assert_eq!(
            format!("{}", unode!(1, 2).as_uint().unwrap_err()),
            "invalid type: array, expected a unsigned integer"
        );
        assert_eq!(
            format!("{}", unode!("one" => 1).as_uint().unwrap_err()),
            "invalid type: object, expected a unsigned integer"
        );
    }

    #[test]
    fn as_float() {
        fn float_test(value: UniNode, val: f64) {
            assert!((value.as_float().unwrap() - val).abs() < f64::EPSILON);
        }
        float_test(unode!(true), 1.0);
        float_test(unode!(false), 0.0);
        float_test(unode!(4i32), 4.0);
        float_test(unode!(5u8), 5.0);
        float_test(unode!(i64::MIN), -9223372036854776000.0);
        float_test(unode!(i64::MAX), 9223372036854776000.0);
        float_test(unode!(u64::MAX), 18446744073709552000.0);
        float_test(unode!(1.1), 1.1);
        float_test(unode!("1"), 1.0);
        float_test(unode!("true"), 1.0);
        float_test(unode!("on"), 1.0);
        float_test(unode!("yes"), 1.0);
        float_test(unode!("0"), 0.0);
        float_test(unode!("false"), 0.0);
        float_test(unode!("off"), 0.0);
        float_test(unode!("no"), 0.0);
        assert_eq!(
            format!("{}", unode!().as_float().unwrap_err()),
            "invalid type: null value, expected a floating point"
        );
        assert_eq!(
            format!("{}", unode!("one").as_float().unwrap_err()),
            "invalid type: string \"one\", expected a floating point"
        );
        assert_eq!(
            format!("{}", unode!(1, 2).as_float().unwrap_err()),
            "invalid type: array, expected a floating point"
        );
        assert_eq!(
            format!("{}", unode!("one" => 1).as_float().unwrap_err()),
            "invalid type: object, expected a floating point"
        );
    }

    #[test]
    fn to_array() {
        assert_eq!(
            unode!(1, 2, 3).to_array().unwrap(),
            vec![unode!(1), unode!(2), unode!(3)]
        );
        assert_eq!(
            format!("{}", unode!(1).to_array().unwrap_err()),
            "invalid type: integer `1`, expected a array"
        );
    }

    #[test]
    fn into_array_node() {
        assert_eq!(unode!(1).into_array(), UniNode::Array(vec![unode!(1)]));
        assert_eq!(
            unode!("hello").into_array(),
            UniNode::Array(vec![unode!("hello")])
        );
        assert_eq!(unode![1, 2].into_array(), unode![1, 2]);
        let obj = unode! {"1" => 1, "2" => 2};
        let v = obj.clone().into_array();
        assert!(v.is_array());
        assert_eq!(v.as_array().unwrap().len(), 1);
        assert!(v.as_array().unwrap().contains(&obj));
    }

    #[test]
    fn as_array() {
        assert_eq!(
            unode!("one", 1).as_array().unwrap(),
            &vec![unode!("one"), unode!(1)]
        );

        let mut arr = unode!("one", 1);
        arr.as_array_mut().unwrap().push(unode!("two"));
        assert_eq!(
            arr.as_array().unwrap(),
            &vec![unode!("one"), unode!(1), unode!("two"),]
        );
    }

    #[test]
    fn as_object() {
        assert_eq!(unode!("one" => 1).as_object().unwrap(), &{
            let mut hm = HashMap::new();
            hm.insert("one".to_string(), UniNode::Integer(1));
            hm
        });

        let mut tbl = unode!("one" => 1);
        tbl.as_object_mut()
            .unwrap()
            .insert("two".to_string(), UniNode::Integer(2));
        assert_eq!(tbl.as_object().unwrap(), &{
            let mut hm = HashMap::new();
            hm.insert("one".to_string(), UniNode::Integer(1));
            hm.insert("two".to_string(), UniNode::Integer(2));
            hm
        });
    }

    #[test]
    fn to_object() {
        assert_eq!(unode!("one" => 1).to_object().unwrap(), {
            let mut hm = HashMap::new();
            hm.insert("one".to_string(), UniNode::Integer(1));
            hm
        });
        assert_eq!(
            format!("{}", unode!(1).to_object().unwrap_err()),
            "invalid type: integer `1`, expected a object"
        );
    }

    #[test]
    fn display() {
        assert_eq!(format!("{}", UniNode::Null), "null");
        assert_eq!(format!("{}", UniNode::Boolean(true)), "true");
        assert_eq!(format!("{}", UniNode::Integer(23)), "23");
        assert_eq!(format!("{}", UniNode::UInteger(55)), "55");
        assert_eq!(format!("{}", UniNode::Float(5.6)), "5.6");
        assert_eq!(
            format!("{}", UniNode::String("hello".to_string())),
            "hello"
        );
        let conf = unode!(false, UniNode::Array(vec![unode!(3.3)]));
        assert_eq!(format!("{}", conf), "[false, [3.3]]");
        let conf = unode! { "one" => true };
        assert_eq!(format!("{}", conf), "{one: true}");
    }
}