From 819e71bc89168f79df42bd8ad6caa9c4ba52a4e6 Mon Sep 17 00:00:00 2001 From: Jaroslav Beran Date: Wed, 3 Jul 2024 02:58:03 +0200 Subject: [PATCH] Implement TryFrom on RpcValue Implements TryFrom<&RpcValue> and TryFrom for various basic types and their references. TryFrom<&RpcValue> would be useful e.g. for RPC requests params parsing, where `RpcMessge::param()` returns `Option<&RpcValue>`. TryFrom can provide more efficient implementation for larger datatypes (Blob, List, Map, String), where the returned value is just moved out of the underlying Box without cloning. --- src/rpcvalue.rs | 407 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 405 insertions(+), 2 deletions(-) diff --git a/src/rpcvalue.rs b/src/rpcvalue.rs index a878d16..323368d 100644 --- a/src/rpcvalue.rs +++ b/src/rpcvalue.rs @@ -201,6 +201,378 @@ impl> From for RpcValue { } } +fn format_err_try_from(expected_type: &str, actual_type: &str) -> String { + format!("Expected type `{expected_type}`, got `{actual_type}`") +} + +impl TryFrom<&Value> for () { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::Null => Ok(()), + _ => Err(format_err_try_from("Null",value.type_name())) + } + } +} + +impl TryFrom for () { + type Error = String; + fn try_from(value: Value) -> Result { + Self::try_from(&value) + } +} + +impl TryFrom<&Value> for bool { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::Bool(val) => Ok(*val), + _ => Err(format_err_try_from("Bool", value.type_name())) + } + } +} + +impl TryFrom for bool { + type Error = String; + fn try_from(value: Value) -> Result { + Self::try_from(&value) + } +} + +impl<'a> TryFrom<&'a Value> for &'a str { + type Error = String; + fn try_from(value: &'a Value) -> Result { + match value { + Value::String(val) => Ok(val.as_str()), + _ => Err(format_err_try_from("String", value.type_name())) + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a String { + type Error = String; + fn try_from(value: &'a Value) -> Result { + match value { + Value::String(val) => Ok(val.as_ref()), + _ => Err(format_err_try_from("String", value.type_name())) + } + } +} + +impl TryFrom<&Value> for String { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::String(val) => Ok(*val.clone()), + _ => Err(format_err_try_from("String", value.type_name())) + } + } +} + +impl TryFrom for String { + type Error = String; + fn try_from(value: Value) -> Result { + match value { + Value::String(val) => Ok(*val), + _ => Err(format_err_try_from("String", value.type_name())) + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a Vec { + type Error = String; + fn try_from(value: &'a Value) -> Result { + match value { + Value::Blob(val) => Ok(val), + _ => Err(format_err_try_from("Blob", value.type_name())) + } + } +} + +impl TryFrom<&Value> for Vec { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::Blob(val) => Ok(*val.clone()), + _ => Err(format_err_try_from("Blob", value.type_name())) + } + } +} + +impl TryFrom for Vec { + type Error = String; + fn try_from(value: Value) -> Result { + match value { + Value::Blob(val) => Ok(*val), + _ => Err(format_err_try_from("Blob", value.type_name())) + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a [u8] { + type Error = String; + fn try_from(value: &'a Value) -> Result { + match value { + Value::Blob(val) => Ok(val.as_slice()), + _ => Err(format_err_try_from("Blob", value.type_name())) + } + } +} + +macro_rules! try_from_integral_value { + ($target:ty) => { + impl TryFrom<&Value> for $target { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::Int(val) => <$target>::try_from(*val).map_err(|e| e.to_string()), + Value::UInt(val) => <$target>::try_from(*val).map_err(|e| e.to_string()), + _ => Err(format_err_try_from("Int or UInt", value.type_name())) + } + } + } + + impl TryFrom for $target { + type Error = String; + fn try_from(value: Value) -> Result { + Self::try_from(&value) + } + } + }; +} + +try_from_integral_value!(i32); +try_from_integral_value!(i64); +try_from_integral_value!(isize); +try_from_integral_value!(usize); +try_from_integral_value!(u32); +try_from_integral_value!(u64); + +impl TryFrom<&Value> for f64 { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::Double(val) => Ok(*val), + _ => Err(format_err_try_from("Double", value.type_name())) + } + } +} + +impl TryFrom for f64 { + type Error = String; + fn try_from(value: Value) -> Result { + Self::try_from(&value) + } +} + +impl TryFrom<&Value> for Decimal { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::Decimal(val) => Ok(*val), + _ => Err(format_err_try_from("Decimal", value.type_name())) + } + } +} + +impl TryFrom for Decimal { + type Error = String; + fn try_from(value: Value) -> Result { + Self::try_from(&value) + } +} + +impl<'a> TryFrom<&'a Value> for &'a List { + type Error = String; + fn try_from(value: &'a Value) -> Result { + match value { + Value::List(val) => Ok(val), + _ => Err(format_err_try_from("List", value.type_name())) + } + } +} + +impl TryFrom<&Value> for List { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::List(val) => Ok(*val.clone()), + _ => Err(format_err_try_from("List", value.type_name())) + } + } +} + +impl TryFrom for List { + type Error = String; + fn try_from(value: Value) -> Result { + match value { + Value::List(val) => Ok(*val), + _ => Err(format_err_try_from("List", value.type_name())) + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a Map { + type Error = String; + fn try_from(value: &'a Value) -> Result { + match value { + Value::Map(val) => Ok(val), + _ => Err(format_err_try_from("Map", value.type_name())) + } + } +} + +impl TryFrom<&Value> for Map { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::Map(val) => Ok(*val.clone()), + _ => Err(format_err_try_from("Map", value.type_name())) + } + } +} + +impl TryFrom for Map { + type Error = String; + fn try_from(value: Value) -> Result { + match value { + Value::Map(val) => Ok(*val), + _ => Err(format_err_try_from("Map", value.type_name())) + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a IMap { + type Error = String; + fn try_from(value: &'a Value) -> Result { + match value { + Value::IMap(val) => Ok(val), + _ => Err(format_err_try_from("IMap", value.type_name())) + } + } +} + +impl TryFrom<&Value> for IMap { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::IMap(val) => Ok(*val.clone()), + _ => Err(format_err_try_from("IMap", value.type_name())) + } + } +} + +impl TryFrom for IMap { + type Error = String; + fn try_from(value: Value) -> Result { + match value { + Value::IMap(val) => Ok(*val), + _ => Err(format_err_try_from("IMap", value.type_name())) + } + } +} + +impl TryFrom<&Value> for datetime::DateTime { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::DateTime(val) => Ok(*val), + _ => Err(format_err_try_from("DateTime", value.type_name())) + } + } +} + +impl TryFrom for datetime::DateTime { + type Error = String; + fn try_from(value: Value) -> Result { + Self::try_from(&value) + } +} + +impl TryFrom<&Value> for chrono::NaiveDateTime { + type Error = String; + fn try_from(value: &Value) -> Result { + match value { + Value::DateTime(val) => Ok(val.to_chrono_naivedatetime()), + _ => Err(format_err_try_from("DateTime", value.type_name())) + } + } +} + +impl TryFrom for chrono::NaiveDateTime { + type Error = String; + fn try_from(value: Value) -> Result { + Self::try_from(&value) + } +} + +// Cannot use generic trait implementation here. +// See `rustc --explain E0210` +macro_rules! try_from_rpc_value_ref { + ($target:ty) => { + impl<'a> TryFrom<&'a RpcValue> for $target { + type Error = String; + fn try_from(value: &'a RpcValue) -> Result { + let inner = &value.value; + inner.try_into() + } + } + }; +} + +macro_rules! try_from_rpc_value { + ($target:ty) => { + impl TryFrom for $target { + type Error = String; + fn try_from(value: RpcValue) -> Result { + value.value.try_into() + } + } + }; +} + +try_from_rpc_value_ref!(()); +try_from_rpc_value_ref!(bool); +try_from_rpc_value!(bool); +try_from_rpc_value_ref!(&'a str); +try_from_rpc_value_ref!(&'a String); +try_from_rpc_value_ref!(String); +try_from_rpc_value!(String); +try_from_rpc_value_ref!(&'a Vec); +try_from_rpc_value_ref!(Vec); +try_from_rpc_value!(Vec); +try_from_rpc_value_ref!(&'a [u8]); +try_from_rpc_value_ref!(i32); +try_from_rpc_value!(i32); +try_from_rpc_value_ref!(i64); +try_from_rpc_value!(i64); +try_from_rpc_value_ref!(isize); +try_from_rpc_value!(isize); +try_from_rpc_value_ref!(usize); +try_from_rpc_value!(usize); +try_from_rpc_value_ref!(u32); +try_from_rpc_value!(u32); +try_from_rpc_value_ref!(u64); +try_from_rpc_value!(u64); +try_from_rpc_value_ref!(f64); +try_from_rpc_value!(f64); +try_from_rpc_value_ref!(Decimal); +try_from_rpc_value!(Decimal); +try_from_rpc_value_ref!(&'a List); +try_from_rpc_value_ref!(List); +try_from_rpc_value!(List); +try_from_rpc_value_ref!(&'a Map); +try_from_rpc_value_ref!(Map); +try_from_rpc_value!(Map); +try_from_rpc_value_ref!(&'a IMap); +try_from_rpc_value_ref!(IMap); +try_from_rpc_value!(IMap); +try_from_rpc_value_ref!(datetime::DateTime); +try_from_rpc_value!(datetime::DateTime); +try_from_rpc_value_ref!(chrono::NaiveDateTime); +try_from_rpc_value!(chrono::NaiveDateTime); + + macro_rules! is_xxx { ($name:ident, $variant:pat) => { pub fn $name(&self) -> bool { @@ -388,7 +760,7 @@ impl RpcValue { } pub fn as_decimal(&self) -> decimal::Decimal { match &self.value { - Value::Decimal(d) => d.clone(), + Value::Decimal(d) => *d, _ => decimal::Decimal::new(0, 0), } } @@ -534,26 +906,48 @@ mod test { fn rpcval_new() { let rv = RpcValue::from(true); assert!(rv.as_bool()); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(true)); + assert_eq!(rv.try_into(), Ok(true)); let rv = RpcValue::from("foo"); assert_eq!(rv.as_str(), "foo"); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok("foo")); let rv = RpcValue::from(&b"bar"[..]); assert_eq!(rv.as_blob(), b"bar"); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(b"bar".as_slice())); let rv = RpcValue::from(123); assert_eq!(rv.as_i32(), 123); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(123)); + assert_eq!(rv.try_into(), Ok(123)); let rv = RpcValue::from(12.3); assert_eq!(rv.as_f64(), 12.3); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(12.3)); + assert_eq!(rv.try_into(), Ok(12.3)); let dt = DateTime::now(); let rv = RpcValue::from(dt); assert_eq!(rv.as_datetime(), dt); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(dt)); + assert_eq!(rv.try_into(), Ok(dt)); let dc = Decimal::new(123, -1); - let rv = RpcValue::from(dc.clone()); + let rv = RpcValue::from(dc); assert_eq!(rv.as_decimal(), dc); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(dc)); + assert_eq!(rv.try_into(), Ok(dc)); let dt = chrono::offset::Utc::now(); let rv = RpcValue::from(dt); assert_eq!(rv.as_datetime().epoch_msec(), dt.timestamp_millis()); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(DateTime::from_datetime(&dt))); + assert_eq!(rv.try_into(), Ok(DateTime::from_datetime(&dt))); let dt = chrono::offset::Local::now(); let rv = RpcValue::from(dt); @@ -565,17 +959,26 @@ mod test { let vec1 = vec![RpcValue::from(123), RpcValue::from("foo")]; let rv = RpcValue::from(vec1.clone()); assert_eq!(rv.as_list(), &vec1); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(&vec1)); + assert_eq!(rv.try_into(), Ok(vec1)); let mut m: Map = BTreeMap::new(); m.insert("foo".to_string(), RpcValue::from(123)); m.insert("bar".to_string(), RpcValue::from("foo")); let rv = RpcValue::from(m.clone()); assert_eq!(rv.as_map(), &m); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(&m)); + assert_eq!(rv.try_into(), Ok(m)); let mut m: BTreeMap = BTreeMap::new(); m.insert(1, RpcValue::from(123)); m.insert(2, RpcValue::from("foo")); let rv = RpcValue::from(m.clone()); assert_eq!(rv.as_imap(), &m); + let rrv = &rv; + assert_eq!(rrv.try_into(), Ok(&m)); + assert_eq!(rv.try_into(), Ok(m)); } }