use serde::Serialize;
use std::collections::HashMap;
mod de;
pub type ValueHash = HashMap<String, Value>;
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
}
}
#[macro_export]
macro_rules! value_hash {
($($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)
);
}
}