use crate::value::graph_entities::{Edge, Node};
use crate::value::path::Path;
use crate::value::point::Point;
use crate::value::temporal::{Date, DateTime, Duration, Time};
use crate::value::vec32::Vec32;
use crate::{FalkorDBError, FalkorResult, FalkorValue};
use std::collections::HashMap;
const F64_EXACT_INT_LIMIT: i64 = 1 << 53;
pub trait FromFalkorValue: Sized {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self>;
}
pub(crate) fn variant_name(value: &FalkorValue) -> &'static str {
match value {
FalkorValue::Node(_) => "Node",
FalkorValue::Edge(_) => "Edge",
FalkorValue::Array(_) => "Array",
FalkorValue::Map(_) => "Map",
FalkorValue::Vec32(_) => "Vec32",
FalkorValue::String(_) => "String",
FalkorValue::Bool(_) => "Bool",
FalkorValue::I64(_) => "I64",
FalkorValue::F64(_) => "F64",
FalkorValue::Point(_) => "Point",
FalkorValue::Path(_) => "Path",
FalkorValue::DateTime(_) => "DateTime",
FalkorValue::Date(_) => "Date",
FalkorValue::Time(_) => "Time",
FalkorValue::Duration(_) => "Duration",
FalkorValue::None => "None",
FalkorValue::Unparseable(_) => "Unparseable",
}
}
fn type_error<T>(
expected: &'static str,
got: &FalkorValue,
) -> FalkorResult<T> {
Err(FalkorDBError::TypeError {
expected,
got: variant_name(got),
})
}
impl FromFalkorValue for FalkorValue {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
Ok(value)
}
}
impl FromFalkorValue for i64 {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::I64(int) => Ok(int),
other => type_error("i64", &other),
}
}
}
macro_rules! impl_narrow_int_from_value {
($($t:ty),* $(,)?) => {$(
impl FromFalkorValue for $t {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::I64(int) => <$t>::try_from(int).map_err(|_| FalkorDBError::TypeError {
expected: stringify!($t),
got: "I64",
}),
other => type_error(stringify!($t), &other),
}
}
}
)*};
}
impl_narrow_int_from_value!(i8, i16, i32, u8, u16, u32, u64, usize, isize);
impl FromFalkorValue for f64 {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::F64(float) => Ok(float),
FalkorValue::I64(int)
if (-F64_EXACT_INT_LIMIT..=F64_EXACT_INT_LIMIT).contains(&int) =>
{
Ok(int as f64)
}
other => type_error("f64", &other),
}
}
}
impl FromFalkorValue for bool {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::Bool(boolean) => Ok(boolean),
other => type_error("bool", &other),
}
}
}
impl FromFalkorValue for String {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::String(string) => Ok(string),
other => type_error("String", &other),
}
}
}
macro_rules! impl_entity_from_value {
($($t:ty => $variant:ident => $name:literal),* $(,)?) => {$(
impl FromFalkorValue for $t {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::$variant(entity) => Ok(entity),
other => type_error($name, &other),
}
}
}
)*};
}
impl_entity_from_value!(
Node => Node => "Node",
Edge => Edge => "Edge",
Point => Point => "Point",
Path => Path => "Path",
Vec32 => Vec32 => "Vec32",
DateTime => DateTime => "DateTime",
Date => Date => "Date",
Time => Time => "Time",
Duration => Duration => "Duration",
);
impl<T: FromFalkorValue> FromFalkorValue for Option<T> {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::None => Ok(None),
other => T::from_falkor_value(other).map(Some),
}
}
}
impl<T: FromFalkorValue> FromFalkorValue for Vec<T> {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::Array(items) => items.into_iter().map(T::from_falkor_value).collect(),
other => type_error("Vec", &other),
}
}
}
impl<T: FromFalkorValue> FromFalkorValue for HashMap<String, T> {
fn from_falkor_value(value: FalkorValue) -> FalkorResult<Self> {
match value {
FalkorValue::Map(map) => map
.into_iter()
.map(|(key, value)| Ok((key, T::from_falkor_value(value)?)))
.collect(),
other => type_error("HashMap", &other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn convert<T: FromFalkorValue>(value: FalkorValue) -> FalkorResult<T> {
T::from_falkor_value(value)
}
#[test]
fn test_scalar_conversions() {
assert_eq!(convert::<i64>(FalkorValue::I64(7)).unwrap(), 7);
assert_eq!(convert::<f64>(FalkorValue::F64(2.5)).unwrap(), 2.5);
assert!(convert::<bool>(FalkorValue::Bool(true)).unwrap());
assert_eq!(
convert::<String>(FalkorValue::String("hi".into())).unwrap(),
"hi"
);
assert_eq!(
convert::<FalkorValue>(FalkorValue::I64(1)).unwrap(),
FalkorValue::I64(1)
);
}
#[test]
fn test_strict_no_coercion() {
assert!(convert::<bool>(FalkorValue::String("true".into())).is_err());
assert!(convert::<i64>(FalkorValue::F64(1.0)).is_err());
}
#[test]
fn test_f64_from_i64_lossless_only() {
assert_eq!(convert::<f64>(FalkorValue::I64(1)).unwrap(), 1.0);
assert_eq!(
convert::<f64>(FalkorValue::I64(F64_EXACT_INT_LIMIT)).unwrap(),
F64_EXACT_INT_LIMIT as f64
);
assert!(convert::<f64>(FalkorValue::I64(F64_EXACT_INT_LIMIT + 1)).is_err());
assert!(convert::<f64>(FalkorValue::I64(i64::MAX)).is_err());
}
#[test]
fn test_narrow_int_range_checked() {
assert_eq!(convert::<i32>(FalkorValue::I64(5)).unwrap(), 5);
assert_eq!(convert::<u32>(FalkorValue::I64(5)).unwrap(), 5);
assert!(convert::<i32>(FalkorValue::I64(i64::MAX)).is_err());
assert!(convert::<u32>(FalkorValue::I64(-1)).is_err());
assert_eq!(
convert::<u32>(FalkorValue::String("x".into())),
Err(FalkorDBError::TypeError {
expected: "u32",
got: "String"
})
);
}
#[test]
fn test_option_and_vec() {
assert_eq!(convert::<Option<i64>>(FalkorValue::None).unwrap(), None);
assert_eq!(
convert::<Option<i64>>(FalkorValue::I64(3)).unwrap(),
Some(3)
);
assert_eq!(
convert::<Vec<i64>>(FalkorValue::Array(vec![
FalkorValue::I64(1),
FalkorValue::I64(2)
]))
.unwrap(),
vec![1, 2]
);
assert!(convert::<Vec<i64>>(FalkorValue::Array(vec![FalkorValue::Bool(true)])).is_err());
}
#[test]
fn test_map_and_entities() {
let mut map = HashMap::new();
map.insert("a".to_string(), FalkorValue::I64(1));
let converted: HashMap<String, i64> = convert(FalkorValue::Map(map)).unwrap();
assert_eq!(converted.get("a"), Some(&1));
assert_eq!(
convert::<HashMap<String, i64>>(FalkorValue::I64(1)),
Err(FalkorDBError::TypeError {
expected: "HashMap",
got: "I64"
})
);
assert!(convert::<Node>(FalkorValue::Node(Node::default())).is_ok());
assert!(convert::<Node>(FalkorValue::I64(1)).is_err());
}
#[test]
fn test_temporal_conversions() {
assert_eq!(
convert::<DateTime>(FalkorValue::DateTime(DateTime::new(5))).unwrap(),
DateTime::new(5)
);
assert_eq!(
convert::<Date>(FalkorValue::Date(Date::new(-1))).unwrap(),
Date::new(-1)
);
assert_eq!(
convert::<Time>(FalkorValue::Time(Time::new(7))).unwrap(),
Time::new(7)
);
assert_eq!(
convert::<Duration>(FalkorValue::Duration(Duration::new(9))).unwrap(),
Duration::new(9)
);
assert_eq!(
convert::<Duration>(FalkorValue::I64(9)),
Err(FalkorDBError::TypeError {
expected: "Duration",
got: "I64"
})
);
}
#[test]
fn test_type_error_reports_variant() {
assert_eq!(
convert::<i64>(FalkorValue::Bool(true)),
Err(FalkorDBError::TypeError {
expected: "i64",
got: "Bool"
})
);
}
#[test]
fn test_variant_name_covers_every_variant() {
use crate::value::graph_entities::{Edge, Node};
use crate::value::path::Path;
use crate::value::point::Point;
use crate::value::temporal::{Date, DateTime, Duration, Time};
use crate::value::vec32::Vec32;
let cases: [(FalkorValue, &str); 17] = [
(FalkorValue::Node(Node::default()), "Node"),
(FalkorValue::Edge(Edge::default()), "Edge"),
(FalkorValue::Array(vec![]), "Array"),
(FalkorValue::Map(HashMap::new()), "Map"),
(FalkorValue::Vec32(Vec32 { values: vec![] }), "Vec32"),
(FalkorValue::String(String::new()), "String"),
(FalkorValue::Bool(true), "Bool"),
(FalkorValue::I64(0), "I64"),
(FalkorValue::F64(0.0), "F64"),
(FalkorValue::Point(Point::default()), "Point"),
(FalkorValue::Path(Path::default()), "Path"),
(FalkorValue::DateTime(DateTime::default()), "DateTime"),
(FalkorValue::Date(Date::default()), "Date"),
(FalkorValue::Time(Time::default()), "Time"),
(FalkorValue::Duration(Duration::default()), "Duration"),
(FalkorValue::None, "None"),
(FalkorValue::Unparseable(String::new()), "Unparseable"),
];
for (value, name) in cases {
assert_eq!(variant_name(&value), name);
}
}
}