#![allow(clippy::inherent_to_string)]
use serde::{Deserialize, Serialize};
use serde_json::{Value as SerdeJsonValue, json};
use std::collections::BTreeMap;
use std::convert::TryFrom;
pub type Children = BTreeMap<String, NodeData>;
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct NodeData {
pub value: Value,
pub updated_at: f64,
}
impl Default for NodeData {
fn default() -> Self {
Self {
value: Value::Null,
updated_at: 0.0,
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub enum Value {
Null,
Bit(bool),
Number(f64),
Text(String),
Link(String),
}
impl Value {
pub fn size(&self) -> usize {
match self {
Value::Text(s) => s.len(),
_ => std::mem::size_of_val(self),
}
}
pub fn to_string(&self) -> String {
match self {
Value::Null => "null".to_string(),
Value::Bit(bool) => {
if *bool {
"true".to_string()
} else {
"false".to_string()
}
}
Value::Number(n) => n.to_string(),
Value::Text(t) => t.clone(),
Value::Link(l) => l.clone(),
}
}
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn is_bit(&self) -> bool {
matches!(self, Value::Bit(_))
}
pub fn is_number(&self) -> bool {
matches!(self, Value::Number(_))
}
pub fn is_text(&self) -> bool {
matches!(self, Value::Text(_))
}
pub fn is_link(&self) -> bool {
matches!(self, Value::Link(_))
}
pub fn as_bit(&self) -> Option<bool> {
match self {
Value::Bit(b) => Some(*b),
_ => None,
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
_ => None,
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
Value::Text(t) => Some(t.as_str()),
_ => None,
}
}
pub fn as_link(&self) -> Option<&str> {
match self {
Value::Link(l) => Some(l.as_str()),
_ => None,
}
}
}
impl TryFrom<SerdeJsonValue> for Value {
type Error = &'static str;
fn try_from(v: SerdeJsonValue) -> Result<Value, Self::Error> {
match v {
SerdeJsonValue::Null => Ok(Value::Null),
SerdeJsonValue::Bool(b) => Ok(Value::Bit(b)),
SerdeJsonValue::String(s) => Ok(Value::Text(s)),
SerdeJsonValue::Number(n) => match n.as_f64() {
Some(n) => Ok(Value::Number(n)),
_ => Err("not convertible to f64"),
},
SerdeJsonValue::Object(obj) => {
if let Some(soul) = obj.get("#").and_then(|v| v.as_str()) {
Ok(Value::Link(soul.to_string()))
} else {
Err("cannot convert json object into Value")
}
}
SerdeJsonValue::Array(_) => Err("cannot convert array into Value"),
}
}
}
impl From<Value> for SerdeJsonValue {
fn from(v: Value) -> SerdeJsonValue {
match v {
Value::Null => SerdeJsonValue::Null,
Value::Text(t) => SerdeJsonValue::String(t),
Value::Bit(b) => SerdeJsonValue::Bool(b),
Value::Number(n) => json!(n),
Value::Link(l) => json!({ "#": l }),
}
}
}
impl From<usize> for Value {
fn from(n: usize) -> Value {
Value::Number(n as f64)
}
}
impl From<f32> for Value {
fn from(n: f32) -> Value {
Value::Number(n as f64)
}
}
impl From<u64> for Value {
fn from(n: u64) -> Value {
Value::Number(n as f64)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Value {
Value::Text(s.to_string())
}
}
impl From<String> for Value {
fn from(s: String) -> Value {
Value::Text(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nodedata_default() {
let nd = NodeData::default();
assert!(nd.value.is_null());
assert_eq!(nd.updated_at, 0.0);
}
#[test]
fn test_nodedata_partial_eq() {
let a = NodeData {
value: Value::Text("x".into()),
updated_at: 1.0,
};
let b = NodeData {
value: Value::Text("x".into()),
updated_at: 1.0,
};
let c = NodeData {
value: Value::Text("y".into()),
updated_at: 1.0,
};
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_value_size_text() {
assert_eq!(Value::Text("hello".into()).size(), 5);
assert_eq!(Value::Text("".into()).size(), 0);
assert_eq!(Value::Text("héllo".into()).size(), 6); }
#[test]
fn test_value_size_non_text() {
assert!(Value::Null.size() > 0);
assert!(Value::Bit(true).size() > 0);
assert!(Value::Number(42.0).size() > 0);
assert!(Value::Link("soul".into()).size() > 0);
}
#[test]
fn test_value_to_string_null() {
assert_eq!(Value::Null.to_string(), "null");
}
#[test]
fn test_value_to_string_bit() {
assert_eq!(Value::Bit(true).to_string(), "true");
assert_eq!(Value::Bit(false).to_string(), "false");
}
#[test]
fn test_value_to_string_number() {
assert_eq!(Value::Number(42.0).to_string(), "42");
assert_eq!(Value::Number(3.15).to_string(), "3.15");
}
#[test]
fn test_value_to_string_text() {
assert_eq!(Value::Text("hello".into()).to_string(), "hello");
}
#[test]
fn test_value_to_string_link() {
assert_eq!(Value::Link("node/abc".into()).to_string(), "node/abc");
}
#[test]
fn test_value_is_null() {
assert!(Value::Null.is_null());
assert!(!Value::Bit(false).is_null());
}
#[test]
fn test_value_is_bit() {
assert!(Value::Bit(true).is_bit());
assert!(!Value::Null.is_bit());
}
#[test]
fn test_value_is_number() {
assert!(Value::Number(1.0).is_number());
assert!(!Value::Null.is_number());
}
#[test]
fn test_value_is_text() {
assert!(Value::Text("x".into()).is_text());
assert!(!Value::Null.is_text());
}
#[test]
fn test_value_is_link() {
assert!(Value::Link("soul".into()).is_link());
assert!(!Value::Null.is_link());
}
#[test]
fn test_value_as_bit() {
assert_eq!(Value::Bit(true).as_bit(), Some(true));
assert_eq!(Value::Bit(false).as_bit(), Some(false));
assert_eq!(Value::Null.as_bit(), None);
}
#[test]
fn test_value_as_number() {
assert_eq!(Value::Number(42.0).as_number(), Some(42.0));
assert_eq!(Value::Null.as_number(), None);
}
#[test]
fn test_value_as_text() {
assert_eq!(Value::Text("hello".into()).as_text(), Some("hello"));
assert_eq!(Value::Null.as_text(), None);
}
#[test]
fn test_value_as_link() {
assert_eq!(Value::Link("soul".into()).as_link(), Some("soul"));
assert_eq!(Value::Null.as_link(), None);
}
#[test]
fn test_try_from_json_null() {
let v = Value::try_from(SerdeJsonValue::Null).unwrap();
assert!(v.is_null());
}
#[test]
fn test_try_from_json_bool() {
let v = Value::try_from(SerdeJsonValue::Bool(true)).unwrap();
assert_eq!(v.as_bit(), Some(true));
}
#[test]
fn test_try_from_json_string() {
let v = Value::try_from(SerdeJsonValue::String("hello".into())).unwrap();
assert_eq!(v.as_text(), Some("hello"));
}
#[test]
fn test_try_from_json_number() {
let v = Value::try_from(serde_json::json!(42.0)).unwrap();
assert_eq!(v.as_number(), Some(42.0));
}
#[test]
fn test_try_from_json_link() {
let json = serde_json::json!({ "#": "node/abc" });
let v = Value::try_from(json).unwrap();
assert_eq!(v.as_link(), Some("node/abc"));
}
#[test]
fn test_try_from_json_array_fails() {
let json = serde_json::json!([1, 2, 3]);
assert!(Value::try_from(json).is_err());
}
#[test]
fn test_try_from_json_object_without_soul_fails() {
let json = serde_json::json!({ "foo": "bar" });
assert!(Value::try_from(json).is_err());
}
#[test]
fn test_roundtrip_null() {
let v = Value::Null;
let json: SerdeJsonValue = v.clone().into();
let v2 = Value::try_from(json).unwrap();
assert_eq!(v, v2);
}
#[test]
fn test_roundtrip_bit() {
let v = Value::Bit(true);
let json: SerdeJsonValue = v.clone().into();
let v2 = Value::try_from(json).unwrap();
assert_eq!(v, v2);
}
#[test]
fn test_roundtrip_text() {
let v = Value::Text("hello world".into());
let json: SerdeJsonValue = v.clone().into();
let v2 = Value::try_from(json).unwrap();
assert_eq!(v, v2);
}
#[test]
fn test_roundtrip_number() {
let v = Value::Number(42.0);
let json: SerdeJsonValue = v.clone().into();
let v2 = Value::try_from(json).unwrap();
assert_eq!(v, v2);
}
#[test]
fn test_roundtrip_link() {
let v = Value::Link("node/abc".into());
let json: SerdeJsonValue = v.clone().into();
let v2 = Value::try_from(json).unwrap();
assert_eq!(v, v2);
}
#[test]
fn test_from_str() {
let v: Value = "hello".into();
assert_eq!(v.as_text(), Some("hello"));
}
#[test]
fn test_from_string() {
let v: Value = String::from("world").into();
assert_eq!(v.as_text(), Some("world"));
}
#[test]
fn test_from_usize() {
let v: Value = 42usize.into();
assert_eq!(v.as_number(), Some(42.0));
}
#[test]
fn test_from_u64() {
let v: Value = 99u64.into();
assert_eq!(v.as_number(), Some(99.0));
}
#[test]
fn test_from_f32() {
let v: Value = 3.15f32.into();
assert!((v.as_number().unwrap() - 3.15).abs() < 0.001);
}
#[test]
fn test_children_btreemap() {
let mut children: Children = BTreeMap::new();
children.insert(
"key1".to_string(),
NodeData {
value: Value::Text("v1".into()),
updated_at: 1.0,
},
);
children.insert(
"key2".to_string(),
NodeData {
value: Value::Text("v2".into()),
updated_at: 2.0,
},
);
let keys: Vec<&String> = children.keys().collect();
assert_eq!(keys, vec!["key1", "key2"]);
}
}