kdb-connection 0.1.0

kDB connection.
Documentation
use serde::Serialize;
use std::collections::HashMap;

mod de;

/// Map of values
pub type ValueHash = HashMap<String, Value>;
/// Array of values
pub type ValueArray = Vec<Value>;

#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
enum ValueData
{
    Null,
    Boolean(bool),
    Integer64(i64),
    Float64(f64),
    Array(ValueArray),
    Map(ValueHash),
    String(String),
}

#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(tag = "type", rename = "literal")]
pub struct Value
{
    datatype: String,
    value: ValueData,
}

impl Value
{
    pub fn from_uri_value<T: Into<Value>>(datatype: impl Into<String>, v: T) -> Self
    {
        let t: Value = v.into();
        Self {
            datatype: datatype.into(),
            value: t.value,
        }
    }
    pub fn datatype_ref(&self) -> &String
    {
        &self.datatype
    }
}

/// Convenient macro for creating ValueMap.
///
/// Example:
///
/// ```rust
/// # use kdb_connection::{value_hash, ValueHash};
/// let value_hash: ValueHash = value_hash!("hello" => 12);
/// ```
#[macro_export]
macro_rules! value_hash {
  // map-like
  ($($k:expr => $v:expr),* $(,)?) => {
    {
      let value_map: $crate::ValueHash = core::convert::From::from([$(($k.to_string(), $v.into()),)*]);
      value_map
    }
  };
}

macro_rules! define_value_field {
    ($name: ident, $type: ident, $uri: expr) => {
        impl<'a> TryFrom<&'a Value> for &'a $type
        {
            type Error = crate::errors::Error;
            fn try_from(value: &'a Value) -> Result<&'a $type, Self::Error>
            {
                match &value.value
                {
                    ValueData::$name(v) => Ok(&v),
                    _ => Err(Self::Error::InvalidValueCast {
                        type_name: stringify!($name),
                        value: format!("{:?}", value),
                    }),
                }
            }
        }
        impl TryInto<$type> for Value
        {
            type Error = crate::errors::Error;
            fn try_into(self) -> Result<$type, Self::Error>
            {
                match self.value
                {
                    ValueData::$name(v) => Ok(v),
                    _ => Err(Self::Error::InvalidValueCast {
                        type_name: stringify!($name),
                        value: format!("{:?}", self),
                    }),
                }
            }
        }

        impl From<$type> for Value
        {
            fn from(value: $type) -> Self
            {
                return Self {
                    datatype: $uri.into(),
                    value: ValueData::$name(value),
                };
            }
        }
    };
}

define_value_field!(Boolean, bool, "http://www.w3.org/2001/XMLSchema#bool");
define_value_field!(Integer64, i64, "http://www.w3.org/2001/XMLSchema#long");
define_value_field!(Float64, f64, "http://www.w3.org/2001/XMLSchema#float64");
define_value_field!(String, String, "http://www.w3.org/2001/XMLSchema#string");
define_value_field!(Map, ValueHash, "http://askco.re/datatype#valuehash");
define_value_field!(Array, ValueArray, "http://askco.re/datatype#valuelist");

impl From<ValueData> for Value
{
    fn from(v: ValueData) -> Self
    {
        let datatype = match &v
        {
            ValueData::Null => "http://www.w3.org/2001/XMLSchema#nil",
            ValueData::Boolean(_) => "http://www.w3.org/2001/XMLSchema#boolean",
            ValueData::Integer64(_) => "http://www.w3.org/2001/XMLSchema#long",
            ValueData::Float64(_) => "http://www.w3.org/2001/XMLSchema#float64",
            ValueData::String(_) => "http://www.w3.org/2001/XMLSchema#string",
            ValueData::Map(_) => "http://askco.re/datatype#valuehash",
            ValueData::Array(_) => "http://askco.re/datatype#valuelist",
        };

        Value {
            datatype: datatype.to_string(),
            value: v,
        }
    }
}

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

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

    #[test]
    fn test_value_integer_serialisation()
    {
        let v: Value = 42.into();
        let vs = serde_json::to_string(&v).unwrap();
        assert_eq!(
            vs,
            "{\"type\":\"literal\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#long\",\"value\":42}"
        );
        let v = serde_json::from_str::<Value>(vs.as_str()).unwrap();
        assert_eq!(v.datatype, "http://www.w3.org/2001/XMLSchema#long");
        let vi: i64 = v.try_into().unwrap();
        assert_eq!(vi, 42);
    }
    #[test]
    fn test_value_hash_serialisation()
    {
        let vh: ValueHash = [("a".into(), 32.into()), ("b".into(), "c".into())].into();
        let v: Value = vh.clone().into();
        let vs = serde_json::to_string(&v).unwrap();
        assert!(
            vs == "{\"type\":\"literal\",\"datatype\":\"http://askco.re/datatype#valuehash\",\"value\":{\"b\":{\"type\":\"literal\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#string\",\"value\":\"c\"},\"a\":{\"type\":\"literal\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#long\",\"value\":32}}}"
                || vs
                    == "{\"type\":\"literal\",\"datatype\":\"http://askco.re/datatype#valuehash\",\"value\":{\"a\":{\"type\":\"literal\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#long\",\"value\":32},\"b\":{\"type\":\"literal\",\"datatype\":\"http://www.w3.org/2001/XMLSchema#string\",\"value\":\"c\"}}}"
        );
        let v = serde_json::from_str::<Value>(vs.as_str()).unwrap();
        assert_eq!(v.datatype, "http://askco.re/datatype#valuehash");
        let vh2: ValueHash = v.try_into().unwrap();
        assert_eq!(vh, vh2);

        let v = serde_json::from_str::<Value>(
            r#"
    {
      "datatype":"http://askco.re/datatype#valuehash",
      "type":"literal",
      "value":{
        "datatype":{
          "datatype":"http://www.w3.org/2001/XMLSchema#string",
          "value":"http://www.w3.org/2001/XMLSchema#long"
        },
        "type":{
          "datatype":"http://www.w3.org/2001/XMLSchema#string",
          "value":"literal"
        },
        "value":{
          "datatype":"http://www.w3.org/2001/XMLSchema#long",
          "value":12
        }
      }
    }"#,
        )
        .unwrap();
        let vh2: ValueHash = v.try_into().unwrap();
        assert_eq!(vh2.len(), 3);
        assert_eq!(
            *vh2.get("datatype").unwrap(),
            "http://www.w3.org/2001/XMLSchema#long".into()
        );
        assert_eq!(*vh2.get("type").unwrap(), "literal".into());
        assert_eq!(
            *vh2.get("value").unwrap(),
            Value::from_uri_value("http://www.w3.org/2001/XMLSchema#long", 12)
        );
    }
}