From ddb118e46623aaa9e38a0433460fcbf0d662f535 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 31 Jan 2020 12:31:43 +1000 Subject: [PATCH 1/5] support coercing Values into primitive types --- Cargo.toml | 4 +- src/kv/value/internal.rs | 252 +++++++++++++++++++++++++++++++++++++++ src/kv/value/mod.rs | 136 +++++++++++++++++++++ src/lib.rs | 1 - 4 files changed, 390 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6361d0df9..ab8a3b57a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,8 +51,8 @@ kv_unstable_sval = ["kv_unstable", "sval/fmt"] [dependencies] cfg-if = "0.1.2" serde = { version = "1.0", optional = true, default-features = false } -sval = { version = "0.4.2", optional = true, default-features = false } +sval = { version = "0.5", optional = true, default-features = false } [dev-dependencies] serde_test = "1.0" -sval = { version = "0.4.2", features = ["test"] } +sval = { version = "0.5", features = ["test"] } diff --git a/src/kv/value/internal.rs b/src/kv/value/internal.rs index f2c22a510..367653c08 100644 --- a/src/kv/value/internal.rs +++ b/src/kv/value/internal.rs @@ -75,6 +75,195 @@ pub(super) enum Primitive<'v> { None, } +mod coerce { + use super::*; + + impl<'v> Inner<'v> { + pub(in crate::kv::value) fn as_str(&self) -> Option<&str> { + if let Inner::Primitive(Primitive::Str(value)) = self { + Some(value) + } else { + self.coerce().into_primitive().into_str() + } + } + + pub(in crate::kv::value) fn as_u64(&self) -> Option { + self.coerce().into_primitive().into_u64() + } + + pub(in crate::kv::value) fn as_i64(&self) -> Option { + self.coerce().into_primitive().into_i64() + } + + pub(in crate::kv::value) fn as_f64(&self) -> Option { + self.coerce().into_primitive().into_f64() + } + + pub(in crate::kv::value) fn as_char(&self) -> Option { + self.coerce().into_primitive().into_char() + } + + pub(in crate::kv::value) fn as_bool(&self) -> Option { + self.coerce().into_primitive().into_bool() + } + + fn coerce(&self) -> Coerced { + struct Coerce<'v>(Coerced<'v>); + + impl<'v> Coerce<'v> { + fn new() -> Self { + Coerce(Coerced::Primitive(Primitive::None)) + } + } + + impl<'v> Visitor for Coerce<'v> { + fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> { + Ok(()) + } + + fn u64(&mut self, v: u64) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Unsigned(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Signed(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Float(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Bool(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Char(v)); + Ok(()) + } + + #[cfg(not(feature = "std"))] + fn str(&mut self, v: &str) -> Result<(), Error> { + Ok(()) + } + + #[cfg(feature = "std")] + fn str(&mut self, v: &str) -> Result<(), Error> { + self.0 = Coerced::String(v.into()); + Ok(()) + } + + fn none(&mut self) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::None); + Ok(()) + } + + #[cfg(feature = "kv_unstable_sval")] + fn sval(&mut self, v: &dyn sval_support::Value) -> Result<(), Error> { + self.0 = sval_support::coerce(v); + Ok(()) + } + } + + let mut coerce = Coerce::new(); + let _ = self.visit(&mut coerce); + coerce.0 + } + } + + pub(super) enum Coerced<'v> { + Primitive(Primitive<'v>), + #[cfg(feature = "std")] + String(String), + } + + impl<'v> Coerced<'v> { + fn into_primitive(self) -> Primitive<'v> { + match self { + Coerced::Primitive(value) => value, + _ => Primitive::None, + } + } + } + + impl<'v> Primitive<'v> { + fn into_str(self) -> Option<&'v str> { + if let Primitive::Str(value) = self { + Some(value) + } else { + None + } + } + + fn into_u64(self) -> Option { + if let Primitive::Unsigned(value) = self { + Some(value) + } else { + None + } + } + + fn into_i64(self) -> Option { + if let Primitive::Signed(value) = self { + Some(value) + } else { + None + } + } + + fn into_f64(self) -> Option { + if let Primitive::Float(value) = self { + Some(value) + } else { + None + } + } + + fn into_char(self) -> Option { + if let Primitive::Char(value) = self { + Some(value) + } else { + None + } + } + + fn into_bool(self) -> Option { + if let Primitive::Bool(value) = self { + Some(value) + } else { + None + } + } + } + + #[cfg(feature = "std")] + mod std_support { + use super::*; + + use std::borrow::Cow; + + impl<'v> Inner<'v> { + pub(in crate::kv::value) fn to_str(&self) -> Option> { + self.coerce().into_string() + } + } + + impl<'v> Coerced<'v> { + pub(super) fn into_string(self) -> Option> { + match self { + Coerced::Primitive(Primitive::Str(value)) => Some(value.into()), + Coerced::String(value) => Some(value.into()), + _ => None, + } + } + } + } +} + mod fmt_support { use super::*; @@ -162,6 +351,7 @@ mod fmt_support { #[cfg(feature = "kv_unstable_sval")] pub(super) mod sval_support { + use super::coerce::Coerced; use super::*; extern crate sval; @@ -245,6 +435,48 @@ pub(super) mod sval_support { } } + pub(super) fn coerce<'v>(v: &dyn sval::Value) -> Coerced<'v> { + struct Coerce<'v>(Coerced<'v>); + + impl<'v> sval::Stream for Coerce<'v> { + fn u64(&mut self, v: u64) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Unsigned(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Signed(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Float(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Char(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Bool(v)); + Ok(()) + } + + #[cfg(feature = "std")] + fn str(&mut self, s: &str) -> sval::stream::Result { + self.0 = Coerced::String(s.into()); + Ok(()) + } + } + + let mut coerce = Coerce(Coerced::Primitive(Primitive::None)); + let _ = sval::stream(&mut coerce, v); + + coerce.0 + } + #[cfg(test)] mod tests { use super::*; @@ -262,5 +494,25 @@ pub(super) mod sval_support { assert_eq!(sval::test::tokens(value), expected); } + + #[test] + fn coersion() { + assert_eq!( + 42u64, + kv::Value::from_sval(&42u64) + .as_u64() + .expect("invalid value") + ); + + assert!(kv::Value::from_sval(&"a string").as_str().is_none()); + + #[cfg(feature = "std")] + assert_eq!( + "a string", + &*kv::Value::from_sval(&"a string") + .to_str() + .expect("invalid value") + ); + } } } diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs index 332170d33..1834f6c96 100644 --- a/src/kv/value/mod.rs +++ b/src/kv/value/mod.rs @@ -110,11 +110,119 @@ impl<'v> Value<'v> { } } + /// Try coerce the value into a borrowed string. + pub fn as_str(&self) -> Option<&str> { + self.inner.as_str() + } + + /// Try coerce the value into a `u8`. + pub fn as_u8(&self) -> Option { + self.inner.as_u64().map(|v| v as u8) + } + + /// Try coerce the value into a `u16`. + pub fn as_u16(&self) -> Option { + self.inner.as_u64().map(|v| v as u16) + } + + /// Try coerce the value into a `u32`. + pub fn as_u32(&self) -> Option { + self.inner.as_u64().map(|v| v as u32) + } + + /// Try coerce the value into a `u64`. + pub fn as_u64(&self) -> Option { + self.inner.as_u64() + } + + /// Try coerce the value into a `i8`. + pub fn as_i8(&self) -> Option { + self.inner.as_i64().map(|v| v as i8) + } + + /// Try coerce the value into a `i16`. + pub fn as_i16(&self) -> Option { + self.inner.as_i64().map(|v| v as i16) + } + + /// Try coerce the value into a `i32`. + pub fn as_i32(&self) -> Option { + self.inner.as_i64().map(|v| v as i32) + } + + /// Try coerce the value into a `i64`. + pub fn as_i64(&self) -> Option { + self.inner.as_i64() + } + + /// Try coerce the value into a `f32`. + pub fn as_f32(&self) -> Option { + self.inner.as_f64().map(|v| v as f32) + } + + /// Try coerce the value into a `f64`. + pub fn as_f64(&self) -> Option { + self.inner.as_f64() + } + + /// Try coerce the vlaue into a `char`. + pub fn as_char(&self) -> Option { + self.inner.as_char() + } + + /// Try coerce the vlaue into a `bool`. + pub fn as_bool(&self) -> Option { + self.inner.as_bool() + } + fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> { self.inner.visit(visitor) } } +#[cfg(feature = "std")] +mod std_support { + use super::*; + + use std::borrow::Cow; + + impl<'v> Value<'v> { + /// Try coerce the value into an owned or borrowed string. + pub fn to_str(&self) -> Option> { + self.inner.to_str() + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn primtive_coercion() { + assert_eq!( + "a string", + "a string" + .to_owned() + .to_value() + .as_str() + .expect("invalid value") + ); + assert_eq!( + "a string", + &*"a string".to_value().to_str().expect("invalid value") + ); + assert_eq!( + "a string", + &*"a string" + .to_owned() + .to_value() + .to_str() + .expect("invalid value") + ); + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -150,4 +258,32 @@ mod tests { let _ = Value::from_fill(&BadFill).to_string(); } + + #[test] + fn primitive_coercion() { + assert_eq!( + "a string", + "a string".to_value().as_str().expect("invalid value") + ); + assert_eq!( + "a string", + Some("a string").to_value().as_str().expect("invalid value") + ); + + assert_eq!(1u8, 1u64.to_value().as_u8().expect("invalid value")); + assert_eq!(1u16, 1u64.to_value().as_u16().expect("invalid value")); + assert_eq!(1u32, 1u64.to_value().as_u32().expect("invalid value")); + assert_eq!(1u64, 1u64.to_value().as_u64().expect("invalid value")); + + assert_eq!(-1i8, -1i64.to_value().as_i8().expect("invalid value")); + assert_eq!(-1i16, -1i64.to_value().as_i16().expect("invalid value")); + assert_eq!(-1i32, -1i64.to_value().as_i32().expect("invalid value")); + assert_eq!(-1i64, -1i64.to_value().as_i64().expect("invalid value")); + + assert!(1f32.to_value().as_f32().is_some(), "invalid value"); + assert!(1f64.to_value().as_f64().is_some(), "invalid value"); + + assert_eq!('a', 'a'.to_value().as_char().expect("invalid value")); + assert_eq!(true, true.to_value().as_bool().expect("invalid value")); + } } diff --git a/src/lib.rs b/src/lib.rs index f13e4a8d7..969b9d0a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1530,7 +1530,6 @@ mod tests { #[cfg(feature = "std")] fn test_error_trait() { use super::SetLoggerError; - use std::error::Error; let e = SetLoggerError(()); assert_eq!( &e.to_string(), From 6743f4a0d2612a81690e7fe79cd6531d2c66e59b Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 31 Jan 2020 15:02:59 +1000 Subject: [PATCH 2/5] make value internal visitor support borrowing --- src/kv/value/internal.rs | 123 ++++++++++++++++++++-------- src/kv/value/mod.rs | 168 ++++++++++++++++++++++++--------------- src/kv/value/test.rs | 2 +- src/lib.rs | 19 +++++ 4 files changed, 216 insertions(+), 96 deletions(-) diff --git a/src/kv/value/internal.rs b/src/kv/value/internal.rs index 367653c08..6b9cbe6f3 100644 --- a/src/kv/value/internal.rs +++ b/src/kv/value/internal.rs @@ -24,17 +24,9 @@ pub(super) enum Inner<'v> { } impl<'v> Inner<'v> { - pub(super) fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> { - match *self { - Inner::Primitive(value) => match value { - Primitive::Signed(value) => visitor.i64(value), - Primitive::Unsigned(value) => visitor.u64(value), - Primitive::Float(value) => visitor.f64(value), - Primitive::Bool(value) => visitor.bool(value), - Primitive::Char(value) => visitor.char(value), - Primitive::Str(value) => visitor.str(value), - Primitive::None => visitor.none(), - }, + pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { + match self { + Inner::Primitive(value) => value.visit(visitor), Inner::Fill(value) => value.fill(&mut Slot::new(visitor)), Inner::Debug(value) => visitor.debug(value), Inner::Display(value) => visitor.display(value), @@ -46,7 +38,7 @@ impl<'v> Inner<'v> { } /// The internal serialization contract. -pub(super) trait Visitor { +pub(super) trait Visitor<'v> { fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error>; fn display(&mut self, v: &dyn fmt::Display) -> Result<(), Error> { self.debug(&format_args!("{}", v)) @@ -57,7 +49,12 @@ pub(super) trait Visitor { fn f64(&mut self, v: f64) -> Result<(), Error>; fn bool(&mut self, v: bool) -> Result<(), Error>; fn char(&mut self, v: char) -> Result<(), Error>; + fn str(&mut self, v: &str) -> Result<(), Error>; + fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { + self.str(v) + } + fn none(&mut self) -> Result<(), Error>; #[cfg(feature = "kv_unstable_sval")] @@ -75,35 +72,45 @@ pub(super) enum Primitive<'v> { None, } +impl<'v> Primitive<'v> { + fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { + match self { + Primitive::Signed(value) => visitor.i64(value), + Primitive::Unsigned(value) => visitor.u64(value), + Primitive::Float(value) => visitor.f64(value), + Primitive::Bool(value) => visitor.bool(value), + Primitive::Char(value) => visitor.char(value), + Primitive::Str(value) => visitor.borrowed_str(value), + Primitive::None => visitor.none(), + } + } +} + mod coerce { use super::*; impl<'v> Inner<'v> { - pub(in crate::kv::value) fn as_str(&self) -> Option<&str> { - if let Inner::Primitive(Primitive::Str(value)) = self { - Some(value) - } else { - self.coerce().into_primitive().into_str() - } + pub(in crate::kv::value) fn get_str(&self) -> Option<&str> { + self.coerce().into_primitive().into_str() } - pub(in crate::kv::value) fn as_u64(&self) -> Option { + pub(in crate::kv::value) fn get_u64(&self) -> Option { self.coerce().into_primitive().into_u64() } - pub(in crate::kv::value) fn as_i64(&self) -> Option { + pub(in crate::kv::value) fn get_i64(&self) -> Option { self.coerce().into_primitive().into_i64() } - pub(in crate::kv::value) fn as_f64(&self) -> Option { + pub(in crate::kv::value) fn get_f64(&self) -> Option { self.coerce().into_primitive().into_f64() } - pub(in crate::kv::value) fn as_char(&self) -> Option { + pub(in crate::kv::value) fn get_char(&self) -> Option { self.coerce().into_primitive().into_char() } - pub(in crate::kv::value) fn as_bool(&self) -> Option { + pub(in crate::kv::value) fn get_bool(&self) -> Option { self.coerce().into_primitive().into_bool() } @@ -116,7 +123,7 @@ mod coerce { } } - impl<'v> Visitor for Coerce<'v> { + impl<'v> Visitor<'v> for Coerce<'v> { fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> { Ok(()) } @@ -146,8 +153,13 @@ mod coerce { Ok(()) } + fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Str(v)); + Ok(()) + } + #[cfg(not(feature = "std"))] - fn str(&mut self, v: &str) -> Result<(), Error> { + fn str(&mut self, _: &str) -> Result<(), Error> { Ok(()) } @@ -185,6 +197,7 @@ mod coerce { fn into_primitive(self) -> Primitive<'v> { match self { Coerced::Primitive(value) => value, + #[cfg(feature = "std")] _ => Primitive::None, } } @@ -247,7 +260,7 @@ mod coerce { use std::borrow::Cow; impl<'v> Inner<'v> { - pub(in crate::kv::value) fn to_str(&self) -> Option> { + pub(in crate::kv::value) fn get_string(&self) -> Option> { self.coerce().into_string() } } @@ -289,6 +302,36 @@ mod fmt_support { } } + impl<'s, 'f> Slot<'s, 'f> { + /// Fill the slot with a debuggable value. + /// + /// The given value doesn't need to satisfy any particular lifetime constraints. + /// + /// # Panics + /// + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_debug(&mut self, value: T) -> Result<(), Error> + where + T: fmt::Debug, + { + self.fill(|visitor| visitor.debug(&value)) + } + + /// Fill the slot with a displayable value. + /// + /// The given value doesn't need to satisfy any particular lifetime constraints. + /// + /// # Panics + /// + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_display(&mut self, value: T) -> Result<(), Error> + where + T: fmt::Display, + { + self.fill(|visitor| visitor.display(&value)) + } + } + impl<'v> fmt::Debug for kv::Value<'v> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.visit(&mut FmtVisitor(f))?; @@ -307,7 +350,7 @@ mod fmt_support { struct FmtVisitor<'a, 'b: 'a>(&'a mut fmt::Formatter<'b>); - impl<'a, 'b: 'a> Visitor for FmtVisitor<'a, 'b> { + impl<'a, 'b: 'a, 'v> Visitor<'v> for FmtVisitor<'a, 'b> { fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { v.fmt(self.0)?; @@ -368,6 +411,22 @@ pub(super) mod sval_support { } } + impl<'s, 'f> Slot<'s, 'f> { + /// Fill the slot with a structured value. + /// + /// The given value doesn't need to satisfy any particular lifetime constraints. + /// + /// # Panics + /// + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_sval(&mut self, value: T) -> Result<(), Error> + where + T: sval::Value, + { + self.fill(|visitor| visitor.sval(&value)) + } + } + impl<'v> sval::Value for kv::Value<'v> { fn stream(&self, s: &mut sval::value::Stream) -> sval::value::Result { self.visit(&mut SvalVisitor(s)).map_err(Error::into_sval)?; @@ -395,7 +454,7 @@ pub(super) mod sval_support { struct SvalVisitor<'a, 'b: 'a>(&'a mut sval::value::Stream<'b>); - impl<'a, 'b: 'a> Visitor for SvalVisitor<'a, 'b> { + impl<'a, 'b: 'a, 'v> Visitor<'v> for SvalVisitor<'a, 'b> { fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { self.0 .fmt(format_args!("{:?}", v)) @@ -496,21 +555,21 @@ pub(super) mod sval_support { } #[test] - fn coersion() { + fn sval_coersion() { assert_eq!( 42u64, kv::Value::from_sval(&42u64) - .as_u64() + .get_u64() .expect("invalid value") ); - assert!(kv::Value::from_sval(&"a string").as_str().is_none()); + assert!(kv::Value::from_sval(&"a string").get_str().is_none()); #[cfg(feature = "std")] assert_eq!( "a string", &*kv::Value::from_sval(&"a string") - .to_str() + .get_string() .expect("invalid value") ); } diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs index 1834f6c96..59bbe02fa 100644 --- a/src/kv/value/mod.rs +++ b/src/kv/value/mod.rs @@ -53,37 +53,47 @@ where } /// A value slot to fill using the [`Fill`](trait.Fill.html) trait. -pub struct Slot<'a> { +pub struct Slot<'s, 'f> { filled: bool, - visitor: &'a mut dyn Visitor, + visitor: &'s mut dyn Visitor<'f>, } -impl<'a> fmt::Debug for Slot<'a> { +impl<'s, 'f> fmt::Debug for Slot<'s, 'f> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Slot").finish() } } -impl<'a> Slot<'a> { - fn new(visitor: &'a mut dyn Visitor) -> Self { +impl<'s, 'f> Slot<'s, 'f> { + fn new(visitor: &'s mut dyn Visitor<'f>) -> Self { Slot { visitor, filled: false, } } + fn fill(&mut self, f: F) -> Result<(), Error> + where + F: FnOnce(&mut dyn Visitor<'f>) -> Result<(), Error>, + { + assert!(!self.filled, "the slot has already been filled"); + self.filled = true; + + f(self.visitor) + } + /// Fill the slot with a value. /// /// The given value doesn't need to satisfy any particular lifetime constraints. /// /// # Panics /// - /// Calling `fill` more than once will panic. - pub fn fill(&mut self, value: Value) -> Result<(), Error> { - assert!(!self.filled, "the slot has already been filled"); - self.filled = true; - - value.visit(self.visitor) + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_any(&mut self, value: T) -> Result<(), Error> + where + T: Into>, + { + self.fill(|visitor| value.into().inner.visit(visitor)) } } @@ -111,71 +121,71 @@ impl<'v> Value<'v> { } /// Try coerce the value into a borrowed string. - pub fn as_str(&self) -> Option<&str> { - self.inner.as_str() + pub fn get_str(&self) -> Option<&str> { + self.inner.get_str() } /// Try coerce the value into a `u8`. - pub fn as_u8(&self) -> Option { - self.inner.as_u64().map(|v| v as u8) + pub fn get_u8(&self) -> Option { + self.inner.get_u64().map(|v| v as u8) } /// Try coerce the value into a `u16`. - pub fn as_u16(&self) -> Option { - self.inner.as_u64().map(|v| v as u16) + pub fn get_u16(&self) -> Option { + self.inner.get_u64().map(|v| v as u16) } /// Try coerce the value into a `u32`. - pub fn as_u32(&self) -> Option { - self.inner.as_u64().map(|v| v as u32) + pub fn get_u32(&self) -> Option { + self.inner.get_u64().map(|v| v as u32) } /// Try coerce the value into a `u64`. - pub fn as_u64(&self) -> Option { - self.inner.as_u64() + pub fn get_u64(&self) -> Option { + self.inner.get_u64() } /// Try coerce the value into a `i8`. - pub fn as_i8(&self) -> Option { - self.inner.as_i64().map(|v| v as i8) + pub fn get_i8(&self) -> Option { + self.inner.get_i64().map(|v| v as i8) } /// Try coerce the value into a `i16`. - pub fn as_i16(&self) -> Option { - self.inner.as_i64().map(|v| v as i16) + pub fn get_i16(&self) -> Option { + self.inner.get_i64().map(|v| v as i16) } /// Try coerce the value into a `i32`. - pub fn as_i32(&self) -> Option { - self.inner.as_i64().map(|v| v as i32) + pub fn get_i32(&self) -> Option { + self.inner.get_i64().map(|v| v as i32) } /// Try coerce the value into a `i64`. - pub fn as_i64(&self) -> Option { - self.inner.as_i64() + pub fn get_i64(&self) -> Option { + self.inner.get_i64() } /// Try coerce the value into a `f32`. - pub fn as_f32(&self) -> Option { - self.inner.as_f64().map(|v| v as f32) + pub fn get_f32(&self) -> Option { + self.inner.get_f64().map(|v| v as f32) } /// Try coerce the value into a `f64`. - pub fn as_f64(&self) -> Option { - self.inner.as_f64() + pub fn get_f64(&self) -> Option { + self.inner.get_f64() } - /// Try coerce the vlaue into a `char`. - pub fn as_char(&self) -> Option { - self.inner.as_char() + /// Try coerce the value into a `char`. + pub fn get_char(&self) -> Option { + self.inner.get_char() } - /// Try coerce the vlaue into a `bool`. - pub fn as_bool(&self) -> Option { - self.inner.as_bool() + /// Try coerce the value into a `bool`. + pub fn get_bool(&self) -> Option { + self.inner.get_bool() } - fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> { + fn visit<'a>(&'a self, visitor: &mut dyn Visitor<'a>) -> Result<(), Error> { self.inner.visit(visitor) } } @@ -188,8 +198,8 @@ mod std_support { impl<'v> Value<'v> { /// Try coerce the value into an owned or borrowed string. - pub fn to_str(&self) -> Option> { - self.inner.to_str() + pub fn get_string(&self) -> Option> { + self.inner.get_string() } } @@ -204,19 +214,19 @@ mod std_support { "a string" .to_owned() .to_value() - .as_str() + .get_str() .expect("invalid value") ); assert_eq!( "a string", - &*"a string".to_value().to_str().expect("invalid value") + &*"a string".to_value().get_string().expect("invalid value") ); assert_eq!( "a string", &*"a string" .to_owned() .to_value() - .to_str() + .get_string() .expect("invalid value") ); } @@ -228,20 +238,31 @@ mod tests { use super::*; #[test] - fn fill_value() { + fn fill_value_borrowed() { struct TestFill; impl Fill for TestFill { fn fill(&self, slot: &mut Slot) -> Result<(), Error> { let dbg: &dyn fmt::Debug = &1; - slot.fill(Value::from_debug(&dbg)) + slot.fill_debug(&dbg) } } assert_eq!("1", Value::from_fill(&TestFill).to_string()); } + #[test] + fn fill_value_owned() { + struct TestFill; + + impl Fill for TestFill { + fn fill(&self, slot: &mut Slot) -> Result<(), Error> { + slot.fill_any("a string") + } + } + } + #[test] #[should_panic] fn fill_multiple_times_panics() { @@ -249,8 +270,8 @@ mod tests { impl Fill for BadFill { fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill(42.into())?; - slot.fill(6789.into())?; + slot.fill_any(42)?; + slot.fill_any(6789)?; Ok(()) } @@ -263,27 +284,48 @@ mod tests { fn primitive_coercion() { assert_eq!( "a string", - "a string".to_value().as_str().expect("invalid value") + "a string".to_value().get_str().expect("invalid value") ); assert_eq!( "a string", - Some("a string").to_value().as_str().expect("invalid value") + Some("a string") + .to_value() + .get_str() + .expect("invalid value") ); - assert_eq!(1u8, 1u64.to_value().as_u8().expect("invalid value")); - assert_eq!(1u16, 1u64.to_value().as_u16().expect("invalid value")); - assert_eq!(1u32, 1u64.to_value().as_u32().expect("invalid value")); - assert_eq!(1u64, 1u64.to_value().as_u64().expect("invalid value")); + assert_eq!(1u8, 1u64.to_value().get_u8().expect("invalid value")); + assert_eq!(1u16, 1u64.to_value().get_u16().expect("invalid value")); + assert_eq!(1u32, 1u64.to_value().get_u32().expect("invalid value")); + assert_eq!(1u64, 1u64.to_value().get_u64().expect("invalid value")); + + assert_eq!(-1i8, -1i64.to_value().get_i8().expect("invalid value")); + assert_eq!(-1i16, -1i64.to_value().get_i16().expect("invalid value")); + assert_eq!(-1i32, -1i64.to_value().get_i32().expect("invalid value")); + assert_eq!(-1i64, -1i64.to_value().get_i64().expect("invalid value")); - assert_eq!(-1i8, -1i64.to_value().as_i8().expect("invalid value")); - assert_eq!(-1i16, -1i64.to_value().as_i16().expect("invalid value")); - assert_eq!(-1i32, -1i64.to_value().as_i32().expect("invalid value")); - assert_eq!(-1i64, -1i64.to_value().as_i64().expect("invalid value")); + assert!(1f32.to_value().get_f32().is_some(), "invalid value"); + assert!(1f64.to_value().get_f64().is_some(), "invalid value"); - assert!(1f32.to_value().as_f32().is_some(), "invalid value"); - assert!(1f64.to_value().as_f64().is_some(), "invalid value"); + assert_eq!('a', 'a'.to_value().get_char().expect("invalid value")); + assert_eq!(true, true.to_value().get_bool().expect("invalid value")); + } + + #[test] + fn fill_coercion() { + struct TestFill; - assert_eq!('a', 'a'.to_value().as_char().expect("invalid value")); - assert_eq!(true, true.to_value().as_bool().expect("invalid value")); + impl Fill for TestFill { + fn fill(&self, slot: &mut Slot) -> Result<(), Error> { + slot.fill_any("a string") + } + } + + assert_eq!( + "a string", + Value::from_fill(&TestFill) + .get_str() + .expect("invalid value") + ); } } diff --git a/src/kv/value/test.rs b/src/kv/value/test.rs index f57715b9d..6a616affe 100644 --- a/src/kv/value/test.rs +++ b/src/kv/value/test.rs @@ -25,7 +25,7 @@ impl<'v> Value<'v> { pub(in kv) fn to_token(&self) -> Token { struct TestVisitor(Option); - impl internal::Visitor for TestVisitor { + impl<'v> internal::Visitor<'v> for TestVisitor { fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { self.0 = Some(Token::Str(format!("{:?}", v))); Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 969b9d0a0..c545be4fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1648,4 +1648,23 @@ mod tests { assert_eq!(2, visitor.seen_pairs); } + + #[test] + #[cfg(feature = "kv_unstable")] + fn test_record_key_values_get_coerce() { + use super::Record; + + let kvs: &[(&str, i32)] = &[("a", 1), ("b", 2)]; + let record = Record::builder().key_values(&kvs).build(); + + assert_eq!( + 1, + record + .key_values() + .get("a".into()) + .expect("missing key") + .get_i32() + .expect("invalid value") + ); + } } From 098e0b2cd6feb0f81516c718315711a998e4b98b Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 31 Jan 2020 17:28:30 +1000 Subject: [PATCH 3/5] refactor value module layout --- src/kv/value/fill.rs | 148 ++++++++ src/kv/value/impls.rs | 5 + src/kv/value/internal.rs | 577 -------------------------------- src/kv/value/internal/coerce.rs | 334 ++++++++++++++++++ src/kv/value/internal/fmt.rs | 123 +++++++ src/kv/value/internal/mod.rs | 94 ++++++ src/kv/value/internal/sval.rs | 189 +++++++++++ src/kv/value/mod.rs | 286 +--------------- src/kv/value/test.rs | 2 +- 9 files changed, 898 insertions(+), 860 deletions(-) create mode 100644 src/kv/value/fill.rs delete mode 100644 src/kv/value/internal.rs create mode 100644 src/kv/value/internal/coerce.rs create mode 100644 src/kv/value/internal/fmt.rs create mode 100644 src/kv/value/internal/mod.rs create mode 100644 src/kv/value/internal/sval.rs diff --git a/src/kv/value/fill.rs b/src/kv/value/fill.rs new file mode 100644 index 000000000..c990a9f94 --- /dev/null +++ b/src/kv/value/fill.rs @@ -0,0 +1,148 @@ +//! Lazy value initialization. + +use std::fmt; + +use super::internal::{Inner, Visitor}; +use super::{Error, Value}; + +impl<'v> Value<'v> { + /// Get a value from a fillable slot. + pub fn from_fill(value: &'v T) -> Self + where + T: Fill, + { + Value { + inner: Inner::Fill(value), + } + } +} + +/// A type that requires extra work to convert into a [`Value`](struct.Value.html). +/// +/// This trait is a more advanced initialization API than [`ToValue`](trait.ToValue.html). +/// It's intended for erased values coming from other logging frameworks that may need +/// to perform extra work to determine the concrete type to use. +pub trait Fill { + /// Fill a value. + fn fill(&self, slot: &mut Slot) -> Result<(), Error>; +} + +impl<'a, T> Fill for &'a T +where + T: Fill + ?Sized, +{ + fn fill(&self, slot: &mut Slot) -> Result<(), Error> { + (**self).fill(slot) + } +} + +/// A value slot to fill using the [`Fill`](trait.Fill.html) trait. +pub struct Slot<'s, 'f> { + filled: bool, + visitor: &'s mut dyn Visitor<'f>, +} + +impl<'s, 'f> fmt::Debug for Slot<'s, 'f> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Slot").finish() + } +} + +impl<'s, 'f> Slot<'s, 'f> { + pub(super) fn new(visitor: &'s mut dyn Visitor<'f>) -> Self { + Slot { + visitor, + filled: false, + } + } + + pub(super) fn fill(&mut self, f: F) -> Result<(), Error> + where + F: FnOnce(&mut dyn Visitor<'f>) -> Result<(), Error>, + { + assert!(!self.filled, "the slot has already been filled"); + self.filled = true; + + f(self.visitor) + } + + /// Fill the slot with a value. + /// + /// The given value doesn't need to satisfy any particular lifetime constraints. + /// + /// # Panics + /// + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_any(&mut self, value: T) -> Result<(), Error> + where + T: Into>, + { + self.fill(|visitor| value.into().inner.visit(visitor)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fill_value_borrowed() { + struct TestFill; + + impl Fill for TestFill { + fn fill(&self, slot: &mut Slot) -> Result<(), Error> { + let dbg: &dyn fmt::Debug = &1; + + slot.fill_debug(&dbg) + } + } + + assert_eq!("1", Value::from_fill(&TestFill).to_string()); + } + + #[test] + fn fill_value_owned() { + struct TestFill; + + impl Fill for TestFill { + fn fill(&self, slot: &mut Slot) -> Result<(), Error> { + slot.fill_any("a string") + } + } + } + + #[test] + #[should_panic] + fn fill_multiple_times_panics() { + struct BadFill; + + impl Fill for BadFill { + fn fill(&self, slot: &mut Slot) -> Result<(), Error> { + slot.fill_any(42)?; + slot.fill_any(6789)?; + + Ok(()) + } + } + + let _ = Value::from_fill(&BadFill).to_string(); + } + + #[test] + fn fill_coercion() { + struct TestFill; + + impl Fill for TestFill { + fn fill(&self, slot: &mut Slot) -> Result<(), Error> { + slot.fill_any("a string") + } + } + + assert_eq!( + "a string", + Value::from_fill(&TestFill) + .get_str() + .expect("invalid value") + ); + } +} diff --git a/src/kv/value/impls.rs b/src/kv/value/impls.rs index 59b181e37..ddf980f9f 100644 --- a/src/kv/value/impls.rs +++ b/src/kv/value/impls.rs @@ -1,3 +1,8 @@ +//! Converting standard types into `Value`s. +//! +//! This module provides `ToValue` implementations for commonly +//! logged types from the standard library. + use std::fmt; use super::{Primitive, ToValue, Value}; diff --git a/src/kv/value/internal.rs b/src/kv/value/internal.rs deleted file mode 100644 index 6b9cbe6f3..000000000 --- a/src/kv/value/internal.rs +++ /dev/null @@ -1,577 +0,0 @@ -use std::fmt; - -use super::{Error, Fill, Slot}; -use kv; - -// `Visitor` is an internal API for visiting the structure of a value. -// It's not intended to be public (at this stage). - -/// A container for a structured value for a specific kind of visitor. -#[derive(Clone, Copy)] -pub(super) enum Inner<'v> { - /// A simple primitive value that can be copied without allocating. - Primitive(Primitive<'v>), - /// A value that can be filled. - Fill(&'v dyn Fill), - /// A debuggable value. - Debug(&'v dyn fmt::Debug), - /// A displayable value. - Display(&'v dyn fmt::Display), - - #[cfg(feature = "kv_unstable_sval")] - /// A structured value from `sval`. - Sval(&'v dyn sval_support::Value), -} - -impl<'v> Inner<'v> { - pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { - match self { - Inner::Primitive(value) => value.visit(visitor), - Inner::Fill(value) => value.fill(&mut Slot::new(visitor)), - Inner::Debug(value) => visitor.debug(value), - Inner::Display(value) => visitor.display(value), - - #[cfg(feature = "kv_unstable_sval")] - Inner::Sval(value) => visitor.sval(value), - } - } -} - -/// The internal serialization contract. -pub(super) trait Visitor<'v> { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error>; - fn display(&mut self, v: &dyn fmt::Display) -> Result<(), Error> { - self.debug(&format_args!("{}", v)) - } - - fn u64(&mut self, v: u64) -> Result<(), Error>; - fn i64(&mut self, v: i64) -> Result<(), Error>; - fn f64(&mut self, v: f64) -> Result<(), Error>; - fn bool(&mut self, v: bool) -> Result<(), Error>; - fn char(&mut self, v: char) -> Result<(), Error>; - - fn str(&mut self, v: &str) -> Result<(), Error>; - fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { - self.str(v) - } - - fn none(&mut self) -> Result<(), Error>; - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn sval_support::Value) -> Result<(), Error>; -} - -#[derive(Clone, Copy)] -pub(super) enum Primitive<'v> { - Signed(i64), - Unsigned(u64), - Float(f64), - Bool(bool), - Char(char), - Str(&'v str), - None, -} - -impl<'v> Primitive<'v> { - fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { - match self { - Primitive::Signed(value) => visitor.i64(value), - Primitive::Unsigned(value) => visitor.u64(value), - Primitive::Float(value) => visitor.f64(value), - Primitive::Bool(value) => visitor.bool(value), - Primitive::Char(value) => visitor.char(value), - Primitive::Str(value) => visitor.borrowed_str(value), - Primitive::None => visitor.none(), - } - } -} - -mod coerce { - use super::*; - - impl<'v> Inner<'v> { - pub(in crate::kv::value) fn get_str(&self) -> Option<&str> { - self.coerce().into_primitive().into_str() - } - - pub(in crate::kv::value) fn get_u64(&self) -> Option { - self.coerce().into_primitive().into_u64() - } - - pub(in crate::kv::value) fn get_i64(&self) -> Option { - self.coerce().into_primitive().into_i64() - } - - pub(in crate::kv::value) fn get_f64(&self) -> Option { - self.coerce().into_primitive().into_f64() - } - - pub(in crate::kv::value) fn get_char(&self) -> Option { - self.coerce().into_primitive().into_char() - } - - pub(in crate::kv::value) fn get_bool(&self) -> Option { - self.coerce().into_primitive().into_bool() - } - - fn coerce(&self) -> Coerced { - struct Coerce<'v>(Coerced<'v>); - - impl<'v> Coerce<'v> { - fn new() -> Self { - Coerce(Coerced::Primitive(Primitive::None)) - } - } - - impl<'v> Visitor<'v> for Coerce<'v> { - fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> { - Ok(()) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Unsigned(v)); - Ok(()) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Signed(v)); - Ok(()) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Float(v)); - Ok(()) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Bool(v)); - Ok(()) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Char(v)); - Ok(()) - } - - fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Str(v)); - Ok(()) - } - - #[cfg(not(feature = "std"))] - fn str(&mut self, _: &str) -> Result<(), Error> { - Ok(()) - } - - #[cfg(feature = "std")] - fn str(&mut self, v: &str) -> Result<(), Error> { - self.0 = Coerced::String(v.into()); - Ok(()) - } - - fn none(&mut self) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::None); - Ok(()) - } - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn sval_support::Value) -> Result<(), Error> { - self.0 = sval_support::coerce(v); - Ok(()) - } - } - - let mut coerce = Coerce::new(); - let _ = self.visit(&mut coerce); - coerce.0 - } - } - - pub(super) enum Coerced<'v> { - Primitive(Primitive<'v>), - #[cfg(feature = "std")] - String(String), - } - - impl<'v> Coerced<'v> { - fn into_primitive(self) -> Primitive<'v> { - match self { - Coerced::Primitive(value) => value, - #[cfg(feature = "std")] - _ => Primitive::None, - } - } - } - - impl<'v> Primitive<'v> { - fn into_str(self) -> Option<&'v str> { - if let Primitive::Str(value) = self { - Some(value) - } else { - None - } - } - - fn into_u64(self) -> Option { - if let Primitive::Unsigned(value) = self { - Some(value) - } else { - None - } - } - - fn into_i64(self) -> Option { - if let Primitive::Signed(value) = self { - Some(value) - } else { - None - } - } - - fn into_f64(self) -> Option { - if let Primitive::Float(value) = self { - Some(value) - } else { - None - } - } - - fn into_char(self) -> Option { - if let Primitive::Char(value) = self { - Some(value) - } else { - None - } - } - - fn into_bool(self) -> Option { - if let Primitive::Bool(value) = self { - Some(value) - } else { - None - } - } - } - - #[cfg(feature = "std")] - mod std_support { - use super::*; - - use std::borrow::Cow; - - impl<'v> Inner<'v> { - pub(in crate::kv::value) fn get_string(&self) -> Option> { - self.coerce().into_string() - } - } - - impl<'v> Coerced<'v> { - pub(super) fn into_string(self) -> Option> { - match self { - Coerced::Primitive(Primitive::Str(value)) => Some(value.into()), - Coerced::String(value) => Some(value.into()), - _ => None, - } - } - } - } -} - -mod fmt_support { - use super::*; - - impl<'v> kv::Value<'v> { - /// Get a value from a debuggable type. - pub fn from_debug(value: &'v T) -> Self - where - T: fmt::Debug, - { - kv::Value { - inner: Inner::Debug(value), - } - } - - /// Get a value from a displayable type. - pub fn from_display(value: &'v T) -> Self - where - T: fmt::Display, - { - kv::Value { - inner: Inner::Display(value), - } - } - } - - impl<'s, 'f> Slot<'s, 'f> { - /// Fill the slot with a debuggable value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_debug(&mut self, value: T) -> Result<(), Error> - where - T: fmt::Debug, - { - self.fill(|visitor| visitor.debug(&value)) - } - - /// Fill the slot with a displayable value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_display(&mut self, value: T) -> Result<(), Error> - where - T: fmt::Display, - { - self.fill(|visitor| visitor.display(&value)) - } - } - - impl<'v> fmt::Debug for kv::Value<'v> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.visit(&mut FmtVisitor(f))?; - - Ok(()) - } - } - - impl<'v> fmt::Display for kv::Value<'v> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.visit(&mut FmtVisitor(f))?; - - Ok(()) - } - } - - struct FmtVisitor<'a, 'b: 'a>(&'a mut fmt::Formatter<'b>); - - impl<'a, 'b: 'a, 'v> Visitor<'v> for FmtVisitor<'a, 'b> { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { - v.fmt(self.0)?; - - Ok(()) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - self.debug(&format_args!("{:?}", v)) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - self.debug(&format_args!("{:?}", v)) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - self.debug(&format_args!("{:?}", v)) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - self.debug(&format_args!("{:?}", v)) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - self.debug(&format_args!("{:?}", v)) - } - - fn str(&mut self, v: &str) -> Result<(), Error> { - self.debug(&format_args!("{:?}", v)) - } - - fn none(&mut self) -> Result<(), Error> { - self.debug(&format_args!("None")) - } - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn sval_support::Value) -> Result<(), Error> { - sval_support::fmt(self.0, v) - } - } -} - -#[cfg(feature = "kv_unstable_sval")] -pub(super) mod sval_support { - use super::coerce::Coerced; - use super::*; - - extern crate sval; - - impl<'v> kv::Value<'v> { - /// Get a value from a structured type. - pub fn from_sval(value: &'v T) -> Self - where - T: sval::Value, - { - kv::Value { - inner: Inner::Sval(value), - } - } - } - - impl<'s, 'f> Slot<'s, 'f> { - /// Fill the slot with a structured value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_sval(&mut self, value: T) -> Result<(), Error> - where - T: sval::Value, - { - self.fill(|visitor| visitor.sval(&value)) - } - } - - impl<'v> sval::Value for kv::Value<'v> { - fn stream(&self, s: &mut sval::value::Stream) -> sval::value::Result { - self.visit(&mut SvalVisitor(s)).map_err(Error::into_sval)?; - - Ok(()) - } - } - - pub(in kv::value) use self::sval::Value; - - pub(super) fn fmt(f: &mut fmt::Formatter, v: &dyn sval::Value) -> Result<(), Error> { - sval::fmt::debug(f, v)?; - Ok(()) - } - - impl Error { - fn from_sval(_: sval::value::Error) -> Self { - Error::msg("`sval` serialization failed") - } - - fn into_sval(self) -> sval::value::Error { - sval::value::Error::msg("`sval` serialization failed") - } - } - - struct SvalVisitor<'a, 'b: 'a>(&'a mut sval::value::Stream<'b>); - - impl<'a, 'b: 'a, 'v> Visitor<'v> for SvalVisitor<'a, 'b> { - fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { - self.0 - .fmt(format_args!("{:?}", v)) - .map_err(Error::from_sval) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - self.0.u64(v).map_err(Error::from_sval) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - self.0.i64(v).map_err(Error::from_sval) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - self.0.f64(v).map_err(Error::from_sval) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - self.0.bool(v).map_err(Error::from_sval) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - self.0.char(v).map_err(Error::from_sval) - } - - fn str(&mut self, v: &str) -> Result<(), Error> { - self.0.str(v).map_err(Error::from_sval) - } - - fn none(&mut self) -> Result<(), Error> { - self.0.none().map_err(Error::from_sval) - } - - fn sval(&mut self, v: &dyn sval::Value) -> Result<(), Error> { - self.0.any(v).map_err(Error::from_sval) - } - } - - pub(super) fn coerce<'v>(v: &dyn sval::Value) -> Coerced<'v> { - struct Coerce<'v>(Coerced<'v>); - - impl<'v> sval::Stream for Coerce<'v> { - fn u64(&mut self, v: u64) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Unsigned(v)); - Ok(()) - } - - fn i64(&mut self, v: i64) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Signed(v)); - Ok(()) - } - - fn f64(&mut self, v: f64) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Float(v)); - Ok(()) - } - - fn char(&mut self, v: char) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Char(v)); - Ok(()) - } - - fn bool(&mut self, v: bool) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Bool(v)); - Ok(()) - } - - #[cfg(feature = "std")] - fn str(&mut self, s: &str) -> sval::stream::Result { - self.0 = Coerced::String(s.into()); - Ok(()) - } - } - - let mut coerce = Coerce(Coerced::Primitive(Primitive::None)); - let _ = sval::stream(&mut coerce, v); - - coerce.0 - } - - #[cfg(test)] - mod tests { - use super::*; - use kv::value::test::Token; - - #[test] - fn test_from_sval() { - assert_eq!(kv::Value::from_sval(&42u64).to_token(), Token::Sval); - } - - #[test] - fn test_sval_structured() { - let value = kv::Value::from(42u64); - let expected = vec![sval::test::Token::Unsigned(42)]; - - assert_eq!(sval::test::tokens(value), expected); - } - - #[test] - fn sval_coersion() { - assert_eq!( - 42u64, - kv::Value::from_sval(&42u64) - .get_u64() - .expect("invalid value") - ); - - assert!(kv::Value::from_sval(&"a string").get_str().is_none()); - - #[cfg(feature = "std")] - assert_eq!( - "a string", - &*kv::Value::from_sval(&"a string") - .get_string() - .expect("invalid value") - ); - } - } -} diff --git a/src/kv/value/internal/coerce.rs b/src/kv/value/internal/coerce.rs new file mode 100644 index 000000000..102489bb3 --- /dev/null +++ b/src/kv/value/internal/coerce.rs @@ -0,0 +1,334 @@ +//! Coerce a `Value` into some concrete types. +//! +//! These operations are cheap when the captured value is a simple primitive, +//! but may end up executing arbitrary caller code if the value is complex. + +use std::fmt; + +use super::{Inner, Primitive, Visitor}; +use crate::kv; +use crate::kv::value::Error; + +impl<'v> kv::Value<'v> { + /// Try coerce the value into a borrowed string. + pub fn get_str(&self) -> Option<&str> { + self.inner.coerce().into_primitive().into_str() + } + + /// Try coerce the value into a `u8`. + pub fn get_u8(&self) -> Option { + self.inner + .coerce() + .into_primitive() + .into_u64() + .map(|v| v as u8) + } + + /// Try coerce the value into a `u16`. + pub fn get_u16(&self) -> Option { + self.inner + .coerce() + .into_primitive() + .into_u64() + .map(|v| v as u16) + } + + /// Try coerce the value into a `u32`. + pub fn get_u32(&self) -> Option { + self.inner + .coerce() + .into_primitive() + .into_u64() + .map(|v| v as u32) + } + + /// Try coerce the value into a `u64`. + pub fn get_u64(&self) -> Option { + self.inner.coerce().into_primitive().into_u64() + } + + /// Try coerce the value into a `i8`. + pub fn get_i8(&self) -> Option { + self.inner + .coerce() + .into_primitive() + .into_i64() + .map(|v| v as i8) + } + + /// Try coerce the value into a `i16`. + pub fn get_i16(&self) -> Option { + self.inner + .coerce() + .into_primitive() + .into_i64() + .map(|v| v as i16) + } + + /// Try coerce the value into a `i32`. + pub fn get_i32(&self) -> Option { + self.inner + .coerce() + .into_primitive() + .into_i64() + .map(|v| v as i32) + } + + /// Try coerce the value into a `i64`. + pub fn get_i64(&self) -> Option { + self.inner.coerce().into_primitive().into_i64() + } + + /// Try coerce the value into a `f32`. + pub fn get_f32(&self) -> Option { + self.inner + .coerce() + .into_primitive() + .into_f64() + .map(|v| v as f32) + } + + /// Try coerce the value into a `f64`. + pub fn get_f64(&self) -> Option { + self.inner.coerce().into_primitive().into_f64() + } + + /// Try coerce the value into a `char`. + pub fn get_char(&self) -> Option { + self.inner.coerce().into_primitive().into_char() + } + + /// Try coerce the value into a `bool`. + pub fn get_bool(&self) -> Option { + self.inner.coerce().into_primitive().into_bool() + } +} + +impl<'v> Inner<'v> { + fn coerce(&self) -> Coerced { + struct Coerce<'v>(Coerced<'v>); + + impl<'v> Coerce<'v> { + fn new() -> Self { + Coerce(Coerced::Primitive(Primitive::None)) + } + } + + impl<'v> Visitor<'v> for Coerce<'v> { + fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> { + Ok(()) + } + + fn u64(&mut self, v: u64) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Unsigned(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Signed(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Float(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Bool(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Char(v)); + Ok(()) + } + + fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::Str(v)); + Ok(()) + } + + #[cfg(not(feature = "std"))] + fn str(&mut self, _: &str) -> Result<(), Error> { + Ok(()) + } + + #[cfg(feature = "std")] + fn str(&mut self, v: &str) -> Result<(), Error> { + self.0 = Coerced::String(v.into()); + Ok(()) + } + + fn none(&mut self) -> Result<(), Error> { + self.0 = Coerced::Primitive(Primitive::None); + Ok(()) + } + + #[cfg(feature = "kv_unstable_sval")] + fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> { + self.0 = super::sval::coerce(v); + Ok(()) + } + } + + let mut coerce = Coerce::new(); + let _ = self.visit(&mut coerce); + coerce.0 + } +} + +pub(super) enum Coerced<'v> { + Primitive(Primitive<'v>), + #[cfg(feature = "std")] + String(String), +} + +impl<'v> Coerced<'v> { + fn into_primitive(self) -> Primitive<'v> { + match self { + Coerced::Primitive(value) => value, + #[cfg(feature = "std")] + _ => Primitive::None, + } + } +} + +impl<'v> Primitive<'v> { + fn into_str(self) -> Option<&'v str> { + if let Primitive::Str(value) = self { + Some(value) + } else { + None + } + } + + fn into_u64(self) -> Option { + if let Primitive::Unsigned(value) = self { + Some(value) + } else { + None + } + } + + fn into_i64(self) -> Option { + if let Primitive::Signed(value) = self { + Some(value) + } else { + None + } + } + + fn into_f64(self) -> Option { + if let Primitive::Float(value) = self { + Some(value) + } else { + None + } + } + + fn into_char(self) -> Option { + if let Primitive::Char(value) = self { + Some(value) + } else { + None + } + } + + fn into_bool(self) -> Option { + if let Primitive::Bool(value) = self { + Some(value) + } else { + None + } + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + + use std::borrow::Cow; + + impl<'v> kv::Value<'v> { + /// Try coerce the value into an owned or borrowed string. + pub fn get_string(&self) -> Option> { + self.inner.coerce().into_string() + } + } + + impl<'v> Coerced<'v> { + pub(super) fn into_string(self) -> Option> { + match self { + Coerced::Primitive(Primitive::Str(value)) => Some(value.into()), + Coerced::String(value) => Some(value.into()), + _ => None, + } + } + } + + #[cfg(test)] + mod tests { + use crate::kv::ToValue; + + #[test] + fn primtive_coercion() { + assert_eq!( + "a string", + "a string" + .to_owned() + .to_value() + .get_str() + .expect("invalid value") + ); + assert_eq!( + "a string", + &*"a string".to_value().get_string().expect("invalid value") + ); + assert_eq!( + "a string", + &*"a string" + .to_owned() + .to_value() + .get_string() + .expect("invalid value") + ); + } + } +} + +#[cfg(test)] +mod tests { + use crate::kv::ToValue; + + #[test] + fn primitive_coercion() { + assert_eq!( + "a string", + "a string".to_value().get_str().expect("invalid value") + ); + assert_eq!( + "a string", + Some("a string") + .to_value() + .get_str() + .expect("invalid value") + ); + + assert_eq!(1u8, 1u64.to_value().get_u8().expect("invalid value")); + assert_eq!(1u16, 1u64.to_value().get_u16().expect("invalid value")); + assert_eq!(1u32, 1u64.to_value().get_u32().expect("invalid value")); + assert_eq!(1u64, 1u64.to_value().get_u64().expect("invalid value")); + + assert_eq!(-1i8, -1i64.to_value().get_i8().expect("invalid value")); + assert_eq!(-1i16, -1i64.to_value().get_i16().expect("invalid value")); + assert_eq!(-1i32, -1i64.to_value().get_i32().expect("invalid value")); + assert_eq!(-1i64, -1i64.to_value().get_i64().expect("invalid value")); + + assert!(1f32.to_value().get_f32().is_some(), "invalid value"); + assert!(1f64.to_value().get_f64().is_some(), "invalid value"); + + assert_eq!('a', 'a'.to_value().get_char().expect("invalid value")); + assert_eq!(true, true.to_value().get_bool().expect("invalid value")); + } +} diff --git a/src/kv/value/internal/fmt.rs b/src/kv/value/internal/fmt.rs new file mode 100644 index 000000000..4f4a019c9 --- /dev/null +++ b/src/kv/value/internal/fmt.rs @@ -0,0 +1,123 @@ +//! Integration between `Value` and `std::fmt`. +//! +//! This module allows any `Value` to implement the `fmt::Debug` and `fmt::Display` traits, +//! and for any `fmt::Debug` or `fmt::Display` to be captured as a `Value`. + +use std::fmt; + +use super::{Inner, Visitor}; +use crate::kv; +use crate::kv::value::{Error, Slot}; + +impl<'v> kv::Value<'v> { + /// Get a value from a debuggable type. + pub fn from_debug(value: &'v T) -> Self + where + T: fmt::Debug, + { + kv::Value { + inner: Inner::Debug(value), + } + } + + /// Get a value from a displayable type. + pub fn from_display(value: &'v T) -> Self + where + T: fmt::Display, + { + kv::Value { + inner: Inner::Display(value), + } + } +} + +impl<'s, 'f> Slot<'s, 'f> { + /// Fill the slot with a debuggable value. + /// + /// The given value doesn't need to satisfy any particular lifetime constraints. + /// + /// # Panics + /// + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_debug(&mut self, value: T) -> Result<(), Error> + where + T: fmt::Debug, + { + self.fill(|visitor| visitor.debug(&value)) + } + + /// Fill the slot with a displayable value. + /// + /// The given value doesn't need to satisfy any particular lifetime constraints. + /// + /// # Panics + /// + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_display(&mut self, value: T) -> Result<(), Error> + where + T: fmt::Display, + { + self.fill(|visitor| visitor.display(&value)) + } +} + +pub(in kv::value) use self::fmt::{Debug, Display}; + +impl<'v> fmt::Debug for kv::Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.visit(&mut FmtVisitor(f))?; + + Ok(()) + } +} + +impl<'v> fmt::Display for kv::Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.visit(&mut FmtVisitor(f))?; + + Ok(()) + } +} + +struct FmtVisitor<'a, 'b: 'a>(&'a mut fmt::Formatter<'b>); + +impl<'a, 'b: 'a, 'v> Visitor<'v> for FmtVisitor<'a, 'b> { + fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { + v.fmt(self.0)?; + + Ok(()) + } + + fn u64(&mut self, v: u64) -> Result<(), Error> { + self.debug(&format_args!("{:?}", v)) + } + + fn i64(&mut self, v: i64) -> Result<(), Error> { + self.debug(&format_args!("{:?}", v)) + } + + fn f64(&mut self, v: f64) -> Result<(), Error> { + self.debug(&format_args!("{:?}", v)) + } + + fn bool(&mut self, v: bool) -> Result<(), Error> { + self.debug(&format_args!("{:?}", v)) + } + + fn char(&mut self, v: char) -> Result<(), Error> { + self.debug(&format_args!("{:?}", v)) + } + + fn str(&mut self, v: &str) -> Result<(), Error> { + self.debug(&format_args!("{:?}", v)) + } + + fn none(&mut self) -> Result<(), Error> { + self.debug(&format_args!("None")) + } + + #[cfg(feature = "kv_unstable_sval")] + fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> { + super::sval::fmt(self.0, v) + } +} diff --git a/src/kv/value/internal/mod.rs b/src/kv/value/internal/mod.rs new file mode 100644 index 000000000..aa108dffe --- /dev/null +++ b/src/kv/value/internal/mod.rs @@ -0,0 +1,94 @@ +//! The internal `Value` serialization API. +//! +//! This implementation isn't intended to be public. It may need to change +//! for optimizations or to support new external serialization frameworks. + +use super::{Error, Fill, Slot}; + +pub(super) mod coerce; +pub(super) mod fmt; +#[cfg(feature = "kv_unstable_sval")] +pub(super) mod sval; + +/// A container for a structured value for a specific kind of visitor. +#[derive(Clone, Copy)] +pub(super) enum Inner<'v> { + /// A simple primitive value that can be copied without allocating. + Primitive(Primitive<'v>), + /// A value that can be filled. + Fill(&'v dyn Fill), + /// A debuggable value. + Debug(&'v dyn fmt::Debug), + /// A displayable value. + Display(&'v dyn fmt::Display), + + #[cfg(feature = "kv_unstable_sval")] + /// A structured value from `sval`. + Sval(&'v dyn sval::Value), +} + +impl<'v> Inner<'v> { + pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { + match self { + Inner::Primitive(value) => value.visit(visitor), + Inner::Fill(value) => value.fill(&mut Slot::new(visitor)), + Inner::Debug(value) => visitor.debug(value), + Inner::Display(value) => visitor.display(value), + + #[cfg(feature = "kv_unstable_sval")] + Inner::Sval(value) => visitor.sval(value), + } + } +} + +/// The internal serialization contract. +pub(super) trait Visitor<'v> { + fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error>; + fn display(&mut self, v: &dyn fmt::Display) -> Result<(), Error> { + self.debug(&format_args!("{}", v)) + } + + fn u64(&mut self, v: u64) -> Result<(), Error>; + fn i64(&mut self, v: i64) -> Result<(), Error>; + fn f64(&mut self, v: f64) -> Result<(), Error>; + fn bool(&mut self, v: bool) -> Result<(), Error>; + fn char(&mut self, v: char) -> Result<(), Error>; + + fn str(&mut self, v: &str) -> Result<(), Error>; + fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { + self.str(v) + } + + fn none(&mut self) -> Result<(), Error>; + + #[cfg(feature = "kv_unstable_sval")] + fn sval(&mut self, v: &dyn sval::Value) -> Result<(), Error>; +} + +/// A captured primitive value. +/// +/// These values are common and cheap to copy around. +#[derive(Clone, Copy)] +pub(super) enum Primitive<'v> { + Signed(i64), + Unsigned(u64), + Float(f64), + Bool(bool), + Char(char), + Str(&'v str), + None, +} + +impl<'v> Primitive<'v> { + fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { + match self { + Primitive::Signed(value) => visitor.i64(value), + Primitive::Unsigned(value) => visitor.u64(value), + Primitive::Float(value) => visitor.f64(value), + Primitive::Bool(value) => visitor.bool(value), + Primitive::Char(value) => visitor.char(value), + Primitive::Str(value) => visitor.borrowed_str(value), + Primitive::None => visitor.none(), + } + } +} diff --git a/src/kv/value/internal/sval.rs b/src/kv/value/internal/sval.rs new file mode 100644 index 000000000..ccd9f6454 --- /dev/null +++ b/src/kv/value/internal/sval.rs @@ -0,0 +1,189 @@ +//! Integration between `Value` and `sval`. +//! +//! This module allows any `Value` to implement the `sval::Value` trait, +//! and for any `sval::Value` to be captured as a `Value`. + +extern crate sval; + +use std::fmt; + +use super::coerce::Coerced; +use super::{Inner, Primitive, Visitor}; +use crate::kv; +use crate::kv::value::{Error, Slot}; + +impl<'v> kv::Value<'v> { + /// Get a value from a structured type. + pub fn from_sval(value: &'v T) -> Self + where + T: sval::Value, + { + kv::Value { + inner: Inner::Sval(value), + } + } +} + +impl<'s, 'f> Slot<'s, 'f> { + /// Fill the slot with a structured value. + /// + /// The given value doesn't need to satisfy any particular lifetime constraints. + /// + /// # Panics + /// + /// Calling more than a single `fill` method on this slot will panic. + pub fn fill_sval(&mut self, value: T) -> Result<(), Error> + where + T: sval::Value, + { + self.fill(|visitor| visitor.sval(&value)) + } +} + +impl<'v> sval::Value for kv::Value<'v> { + fn stream(&self, s: &mut sval::value::Stream) -> sval::value::Result { + self.visit(&mut SvalVisitor(s)).map_err(Error::into_sval)?; + + Ok(()) + } +} + +pub(in kv::value) use self::sval::Value; + +pub(super) fn fmt(f: &mut fmt::Formatter, v: &dyn sval::Value) -> Result<(), Error> { + sval::fmt::debug(f, v)?; + Ok(()) +} + +pub(super) fn coerce<'v>(v: &dyn sval::Value) -> Coerced<'v> { + struct Coerce<'v>(Coerced<'v>); + + impl<'v> sval::Stream for Coerce<'v> { + fn u64(&mut self, v: u64) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Unsigned(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Signed(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Float(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Char(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> sval::stream::Result { + self.0 = Coerced::Primitive(Primitive::Bool(v)); + Ok(()) + } + + #[cfg(feature = "std")] + fn str(&mut self, s: &str) -> sval::stream::Result { + self.0 = Coerced::String(s.into()); + Ok(()) + } + } + + let mut coerce = Coerce(Coerced::Primitive(Primitive::None)); + let _ = sval::stream(&mut coerce, v); + + coerce.0 +} + +impl Error { + fn from_sval(_: sval::value::Error) -> Self { + Error::msg("`sval` serialization failed") + } + + fn into_sval(self) -> sval::value::Error { + sval::value::Error::msg("`sval` serialization failed") + } +} + +struct SvalVisitor<'a, 'b: 'a>(&'a mut sval::value::Stream<'b>); + +impl<'a, 'b: 'a, 'v> Visitor<'v> for SvalVisitor<'a, 'b> { + fn debug(&mut self, v: &dyn fmt::Debug) -> Result<(), Error> { + self.0 + .fmt(format_args!("{:?}", v)) + .map_err(Error::from_sval) + } + + fn u64(&mut self, v: u64) -> Result<(), Error> { + self.0.u64(v).map_err(Error::from_sval) + } + + fn i64(&mut self, v: i64) -> Result<(), Error> { + self.0.i64(v).map_err(Error::from_sval) + } + + fn f64(&mut self, v: f64) -> Result<(), Error> { + self.0.f64(v).map_err(Error::from_sval) + } + + fn bool(&mut self, v: bool) -> Result<(), Error> { + self.0.bool(v).map_err(Error::from_sval) + } + + fn char(&mut self, v: char) -> Result<(), Error> { + self.0.char(v).map_err(Error::from_sval) + } + + fn str(&mut self, v: &str) -> Result<(), Error> { + self.0.str(v).map_err(Error::from_sval) + } + + fn none(&mut self) -> Result<(), Error> { + self.0.none().map_err(Error::from_sval) + } + + fn sval(&mut self, v: &dyn sval::Value) -> Result<(), Error> { + self.0.any(v).map_err(Error::from_sval) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use kv::value::test::Token; + + #[test] + fn test_from_sval() { + assert_eq!(kv::Value::from_sval(&42u64).to_token(), Token::Sval); + } + + #[test] + fn test_sval_structured() { + let value = kv::Value::from(42u64); + let expected = vec![sval::test::Token::Unsigned(42)]; + + assert_eq!(sval::test::tokens(value), expected); + } + + #[test] + fn sval_coersion() { + assert_eq!( + 42u64, + kv::Value::from_sval(&42u64) + .get_u64() + .expect("invalid value") + ); + + assert!(kv::Value::from_sval(&"a string").get_str().is_none()); + + #[cfg(feature = "std")] + assert_eq!( + "a string", + &*kv::Value::from_sval(&"a string") + .get_string() + .expect("invalid value") + ); + } +} diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs index 59bbe02fa..ce609a6ea 100644 --- a/src/kv/value/mod.rs +++ b/src/kv/value/mod.rs @@ -1,13 +1,13 @@ //! Structured values. -use std::fmt; - +mod fill; mod impls; mod internal; #[cfg(test)] pub(in kv) mod test; +pub use self::fill::{Fill, Slot}; pub use kv::Error; use self::internal::{Inner, Primitive, Visitor}; @@ -33,299 +33,21 @@ impl<'v> ToValue for Value<'v> { } } -/// A type that requires extra work to convert into a [`Value`](struct.Value.html). -/// -/// This trait is a more advanced initialization API than [`ToValue`](trait.ToValue.html). -/// It's intended for erased values coming from other logging frameworks that may need -/// to perform extra work to determine the concrete type to use. -pub trait Fill { - /// Fill a value. - fn fill(&self, slot: &mut Slot) -> Result<(), Error>; -} - -impl<'a, T> Fill for &'a T -where - T: Fill + ?Sized, -{ - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - (**self).fill(slot) - } -} - -/// A value slot to fill using the [`Fill`](trait.Fill.html) trait. -pub struct Slot<'s, 'f> { - filled: bool, - visitor: &'s mut dyn Visitor<'f>, -} - -impl<'s, 'f> fmt::Debug for Slot<'s, 'f> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Slot").finish() - } -} - -impl<'s, 'f> Slot<'s, 'f> { - fn new(visitor: &'s mut dyn Visitor<'f>) -> Self { - Slot { - visitor, - filled: false, - } - } - - fn fill(&mut self, f: F) -> Result<(), Error> - where - F: FnOnce(&mut dyn Visitor<'f>) -> Result<(), Error>, - { - assert!(!self.filled, "the slot has already been filled"); - self.filled = true; - - f(self.visitor) - } - - /// Fill the slot with a value. - /// - /// The given value doesn't need to satisfy any particular lifetime constraints. - /// - /// # Panics - /// - /// Calling more than a single `fill` method on this slot will panic. - pub fn fill_any(&mut self, value: T) -> Result<(), Error> - where - T: Into>, - { - self.fill(|visitor| value.into().inner.visit(visitor)) - } -} - /// A value in a structured key-value pair. pub struct Value<'v> { inner: Inner<'v>, } impl<'v> Value<'v> { - /// Get a value from an internal `Visit`. + /// Get a value from an internal primitive. fn from_primitive(value: Primitive<'v>) -> Self { Value { inner: Inner::Primitive(value), } } - /// Get a value from a fillable slot. - pub fn from_fill(value: &'v T) -> Self - where - T: Fill, - { - Value { - inner: Inner::Fill(value), - } - } - - /// Try coerce the value into a borrowed string. - pub fn get_str(&self) -> Option<&str> { - self.inner.get_str() - } - - /// Try coerce the value into a `u8`. - pub fn get_u8(&self) -> Option { - self.inner.get_u64().map(|v| v as u8) - } - - /// Try coerce the value into a `u16`. - pub fn get_u16(&self) -> Option { - self.inner.get_u64().map(|v| v as u16) - } - - /// Try coerce the value into a `u32`. - pub fn get_u32(&self) -> Option { - self.inner.get_u64().map(|v| v as u32) - } - - /// Try coerce the value into a `u64`. - pub fn get_u64(&self) -> Option { - self.inner.get_u64() - } - - /// Try coerce the value into a `i8`. - pub fn get_i8(&self) -> Option { - self.inner.get_i64().map(|v| v as i8) - } - - /// Try coerce the value into a `i16`. - pub fn get_i16(&self) -> Option { - self.inner.get_i64().map(|v| v as i16) - } - - /// Try coerce the value into a `i32`. - pub fn get_i32(&self) -> Option { - self.inner.get_i64().map(|v| v as i32) - } - - /// Try coerce the value into a `i64`. - pub fn get_i64(&self) -> Option { - self.inner.get_i64() - } - - /// Try coerce the value into a `f32`. - pub fn get_f32(&self) -> Option { - self.inner.get_f64().map(|v| v as f32) - } - - /// Try coerce the value into a `f64`. - pub fn get_f64(&self) -> Option { - self.inner.get_f64() - } - - /// Try coerce the value into a `char`. - pub fn get_char(&self) -> Option { - self.inner.get_char() - } - - /// Try coerce the value into a `bool`. - pub fn get_bool(&self) -> Option { - self.inner.get_bool() - } - + /// Visit the value using an internal visitor. fn visit<'a>(&'a self, visitor: &mut dyn Visitor<'a>) -> Result<(), Error> { self.inner.visit(visitor) } } - -#[cfg(feature = "std")] -mod std_support { - use super::*; - - use std::borrow::Cow; - - impl<'v> Value<'v> { - /// Try coerce the value into an owned or borrowed string. - pub fn get_string(&self) -> Option> { - self.inner.get_string() - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn primtive_coercion() { - assert_eq!( - "a string", - "a string" - .to_owned() - .to_value() - .get_str() - .expect("invalid value") - ); - assert_eq!( - "a string", - &*"a string".to_value().get_string().expect("invalid value") - ); - assert_eq!( - "a string", - &*"a string" - .to_owned() - .to_value() - .get_string() - .expect("invalid value") - ); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fill_value_borrowed() { - struct TestFill; - - impl Fill for TestFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - let dbg: &dyn fmt::Debug = &1; - - slot.fill_debug(&dbg) - } - } - - assert_eq!("1", Value::from_fill(&TestFill).to_string()); - } - - #[test] - fn fill_value_owned() { - struct TestFill; - - impl Fill for TestFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill_any("a string") - } - } - } - - #[test] - #[should_panic] - fn fill_multiple_times_panics() { - struct BadFill; - - impl Fill for BadFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill_any(42)?; - slot.fill_any(6789)?; - - Ok(()) - } - } - - let _ = Value::from_fill(&BadFill).to_string(); - } - - #[test] - fn primitive_coercion() { - assert_eq!( - "a string", - "a string".to_value().get_str().expect("invalid value") - ); - assert_eq!( - "a string", - Some("a string") - .to_value() - .get_str() - .expect("invalid value") - ); - - assert_eq!(1u8, 1u64.to_value().get_u8().expect("invalid value")); - assert_eq!(1u16, 1u64.to_value().get_u16().expect("invalid value")); - assert_eq!(1u32, 1u64.to_value().get_u32().expect("invalid value")); - assert_eq!(1u64, 1u64.to_value().get_u64().expect("invalid value")); - - assert_eq!(-1i8, -1i64.to_value().get_i8().expect("invalid value")); - assert_eq!(-1i16, -1i64.to_value().get_i16().expect("invalid value")); - assert_eq!(-1i32, -1i64.to_value().get_i32().expect("invalid value")); - assert_eq!(-1i64, -1i64.to_value().get_i64().expect("invalid value")); - - assert!(1f32.to_value().get_f32().is_some(), "invalid value"); - assert!(1f64.to_value().get_f64().is_some(), "invalid value"); - - assert_eq!('a', 'a'.to_value().get_char().expect("invalid value")); - assert_eq!(true, true.to_value().get_bool().expect("invalid value")); - } - - #[test] - fn fill_coercion() { - struct TestFill; - - impl Fill for TestFill { - fn fill(&self, slot: &mut Slot) -> Result<(), Error> { - slot.fill_any("a string") - } - } - - assert_eq!( - "a string", - Value::from_fill(&TestFill) - .get_str() - .expect("invalid value") - ); - } -} diff --git a/src/kv/value/test.rs b/src/kv/value/test.rs index 6a616affe..ab5f8075e 100644 --- a/src/kv/value/test.rs +++ b/src/kv/value/test.rs @@ -67,7 +67,7 @@ impl<'v> Value<'v> { } #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, _: &dyn internal::sval_support::Value) -> Result<(), Error> { + fn sval(&mut self, _: &dyn internal::sval::Value) -> Result<(), Error> { self.0 = Some(Token::Sval); Ok(()) } From 76cb179decd8ff6046005b64a3951dc6519739ec Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Fri, 31 Jan 2020 17:33:43 +1000 Subject: [PATCH 4/5] make the get test use borrowed data --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c545be4fd..b711146f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1654,16 +1654,16 @@ mod tests { fn test_record_key_values_get_coerce() { use super::Record; - let kvs: &[(&str, i32)] = &[("a", 1), ("b", 2)]; + let kvs: &[(&str, &str)] = &[("a", "1"), ("b", "2")]; let record = Record::builder().key_values(&kvs).build(); assert_eq!( - 1, + "2", record .key_values() - .get("a".into()) + .get("b".into()) .expect("missing key") - .get_i32() + .get_str() .expect("invalid value") ); } From 66af8f2d0cd900d49bb1e995f58df9dff44f62db Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Sat, 1 Feb 2020 12:42:41 +1000 Subject: [PATCH 5/5] refactor coerce into cast revert to to_ inherent methods support downcasting erased values --- src/kv/value/fill.rs | 10 +- src/kv/value/impls.rs | 210 +++----------- src/kv/value/internal/cast.rs | 475 ++++++++++++++++++++++++++++++++ src/kv/value/internal/coerce.rs | 334 ---------------------- src/kv/value/internal/fmt.rs | 34 ++- src/kv/value/internal/mod.rs | 105 ++++++- src/kv/value/internal/sval.rs | 49 ++-- src/kv/value/mod.rs | 7 +- src/lib.rs | 2 +- 9 files changed, 680 insertions(+), 546 deletions(-) create mode 100644 src/kv/value/internal/cast.rs delete mode 100644 src/kv/value/internal/coerce.rs diff --git a/src/kv/value/fill.rs b/src/kv/value/fill.rs index c990a9f94..9642132f4 100644 --- a/src/kv/value/fill.rs +++ b/src/kv/value/fill.rs @@ -2,17 +2,17 @@ use std::fmt; -use super::internal::{Inner, Visitor}; +use super::internal::{Erased, Inner, Visitor}; use super::{Error, Value}; impl<'v> Value<'v> { /// Get a value from a fillable slot. pub fn from_fill(value: &'v T) -> Self where - T: Fill, + T: Fill + 'static, { Value { - inner: Inner::Fill(value), + inner: Inner::Fill(unsafe { Erased::new_unchecked::(value) }), } } } @@ -129,7 +129,7 @@ mod tests { } #[test] - fn fill_coercion() { + fn fill_cast() { struct TestFill; impl Fill for TestFill { @@ -141,7 +141,7 @@ mod tests { assert_eq!( "a string", Value::from_fill(&TestFill) - .get_str() + .to_borrowed_str() .expect("invalid value") ); } diff --git a/src/kv/value/impls.rs b/src/kv/value/impls.rs index ddf980f9f..23e8b1bed 100644 --- a/src/kv/value/impls.rs +++ b/src/kv/value/impls.rs @@ -7,183 +7,45 @@ use std::fmt; use super::{Primitive, ToValue, Value}; -impl ToValue for usize { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: usize) -> Self { - Value::from_primitive(Primitive::Unsigned(value as u64)) - } -} - -impl ToValue for isize { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: isize) -> Self { - Value::from_primitive(Primitive::Signed(value as i64)) - } -} - -impl ToValue for u8 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: u8) -> Self { - Value::from_primitive(Primitive::Unsigned(value as u64)) - } -} - -impl ToValue for u16 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: u16) -> Self { - Value::from_primitive(Primitive::Unsigned(value as u64)) - } -} - -impl ToValue for u32 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: u32) -> Self { - Value::from_primitive(Primitive::Unsigned(value as u64)) - } -} - -impl ToValue for u64 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: u64) -> Self { - Value::from_primitive(Primitive::Unsigned(value)) - } -} - -impl ToValue for i8 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: i8) -> Self { - Value::from_primitive(Primitive::Signed(value as i64)) - } -} - -impl ToValue for i16 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: i16) -> Self { - Value::from_primitive(Primitive::Signed(value as i64)) - } -} - -impl ToValue for i32 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: i32) -> Self { - Value::from_primitive(Primitive::Signed(value as i64)) - } -} - -impl ToValue for i64 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: i64) -> Self { - Value::from_primitive(Primitive::Signed(value)) - } -} - -impl ToValue for f32 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: f32) -> Self { - Value::from_primitive(Primitive::Float(value as f64)) - } -} - -impl ToValue for f64 { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: f64) -> Self { - Value::from_primitive(Primitive::Float(value)) - } -} - -impl ToValue for bool { - fn to_value(&self) -> Value { - Value::from(*self) - } -} - -impl<'v> From for Value<'v> { - fn from(value: bool) -> Self { - Value::from_primitive(Primitive::Bool(value)) - } +macro_rules! impl_into_owned { + ($($into_ty:ty => $convert:ident,)*) => { + $( + impl ToValue for $into_ty { + fn to_value(&self) -> Value { + Value::from(*self) + } + } + + impl<'v> From<$into_ty> for Value<'v> { + fn from(value: $into_ty) -> Self { + Value::from_primitive(value as $convert) + } + } + )* + }; } -impl ToValue for char { +impl<'v> ToValue for &'v str { fn to_value(&self) -> Value { Value::from(*self) } } -impl<'v> From for Value<'v> { - fn from(value: char) -> Self { - Value::from_primitive(Primitive::Char(value)) +impl<'v> From<&'v str> for Value<'v> { + fn from(value: &'v str) -> Self { + Value::from_primitive(value) } } -impl<'v> ToValue for &'v str { +impl<'v> ToValue for fmt::Arguments<'v> { fn to_value(&self) -> Value { Value::from(*self) } } -impl<'v> From<&'v str> for Value<'v> { - fn from(value: &'v str) -> Self { - Value::from_primitive(Primitive::Str(value)) +impl<'v> From> for Value<'v> { + fn from(value: fmt::Arguments<'v>) -> Self { + Value::from_primitive(value) } } @@ -205,11 +67,25 @@ where } } -impl<'v> ToValue for fmt::Arguments<'v> { - fn to_value(&self) -> Value { - Value::from_debug(self) - } -} +impl_into_owned! [ + usize => u64, + u8 => u64, + u16 => u64, + u32 => u64, + u64 => u64, + + isize => i64, + i8 => i64, + i16 => i64, + i32 => i64, + i64 => i64, + + f32 => f64, + f64 => f64, + + char => char, + bool => bool, +]; #[cfg(feature = "std")] mod std_support { diff --git a/src/kv/value/internal/cast.rs b/src/kv/value/internal/cast.rs new file mode 100644 index 000000000..d2aa86e63 --- /dev/null +++ b/src/kv/value/internal/cast.rs @@ -0,0 +1,475 @@ +//! Coerce a `Value` into some concrete types. +//! +//! These operations are cheap when the captured value is a simple primitive, +//! but may end up executing arbitrary caller code if the value is complex. +//! They will also attempt to downcast erased types into a primitive where possible. + +use std::any::TypeId; +use std::fmt; + +use super::{Erased, Inner, Primitive, Visitor}; +use crate::kv::value::{Error, Value}; + +impl<'v> Value<'v> { + /// Try get a `usize` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_usize(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_u64() + .map(|v| v as usize) + } + + /// Try get a `u8` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_u8(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_u64() + .map(|v| v as u8) + } + + /// Try get a `u16` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_u16(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_u64() + .map(|v| v as u16) + } + + /// Try get a `u32` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_u32(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_u64() + .map(|v| v as u32) + } + + /// Try get a `u64` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_u64(&self) -> Option { + self.inner.cast().into_primitive().into_u64() + } + + /// Try get a `isize` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_isize(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_i64() + .map(|v| v as isize) + } + + /// Try get a `i8` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_i8(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_i64() + .map(|v| v as i8) + } + + /// Try get a `i16` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_i16(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_i64() + .map(|v| v as i16) + } + + /// Try get a `i32` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_i32(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_i64() + .map(|v| v as i32) + } + + /// Try get a `i64` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_i64(&self) -> Option { + self.inner.cast().into_primitive().into_i64() + } + + /// Try get a `f32` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_f32(&self) -> Option { + self.inner + .cast() + .into_primitive() + .into_f64() + .map(|v| v as f32) + } + + /// Try get a `f64` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_f64(&self) -> Option { + self.inner.cast().into_primitive().into_f64() + } + + /// Try get a `bool` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_bool(&self) -> Option { + self.inner.cast().into_primitive().into_bool() + } + + /// Try get a `char` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. + pub fn to_char(&self) -> Option { + self.inner.cast().into_primitive().into_char() + } + + /// Try get a `str` from this value. + /// + /// This method is cheap for primitive types. It won't allocate an owned + /// `String` if the value is a complex type. + pub fn to_borrowed_str(&self) -> Option<&str> { + self.inner.cast().into_primitive().into_borrowed_str() + } +} + +impl<'v> Inner<'v> { + /// Cast the inner value to another type. + fn cast(self) -> Cast<'v> { + struct CastVisitor<'v>(Cast<'v>); + + impl<'v> Visitor<'v> for CastVisitor<'v> { + fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> { + Ok(()) + } + + fn u64(&mut self, v: u64) -> Result<(), Error> { + self.0 = Cast::Primitive(Primitive::Unsigned(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> Result<(), Error> { + self.0 = Cast::Primitive(Primitive::Signed(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> Result<(), Error> { + self.0 = Cast::Primitive(Primitive::Float(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> Result<(), Error> { + self.0 = Cast::Primitive(Primitive::Bool(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> Result<(), Error> { + self.0 = Cast::Primitive(Primitive::Char(v)); + Ok(()) + } + + fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { + self.0 = Cast::Primitive(Primitive::Str(v)); + Ok(()) + } + + #[cfg(not(feature = "std"))] + fn str(&mut self, _: &str) -> Result<(), Error> { + Ok(()) + } + + #[cfg(feature = "std")] + fn str(&mut self, v: &str) -> Result<(), Error> { + self.0 = Cast::String(v.into()); + Ok(()) + } + + fn none(&mut self) -> Result<(), Error> { + self.0 = Cast::Primitive(Primitive::None); + Ok(()) + } + + #[cfg(feature = "kv_unstable_sval")] + fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> { + self.0 = super::sval::cast(v); + Ok(()) + } + } + + // Try downcast an erased value first + // It also lets us avoid the Visitor infrastructure for simple primitives + let primitive = match self { + Inner::Primitive(value) => Some(value), + Inner::Fill(value) => value.downcast_primitive(), + Inner::Debug(value) => value.downcast_primitive(), + Inner::Display(value) => value.downcast_primitive(), + + #[cfg(feature = "sval")] + Inner::Sval(value) => value.downcast_primitive(), + }; + + primitive.map(Cast::Primitive).unwrap_or_else(|| { + // If the erased value isn't a primitive then we visit it + let mut cast = CastVisitor(Cast::Primitive(Primitive::None)); + let _ = self.visit(&mut cast); + cast.0 + }) + } +} + +pub(super) enum Cast<'v> { + Primitive(Primitive<'v>), + #[cfg(feature = "std")] + String(String), +} + +impl<'v> Cast<'v> { + fn into_primitive(self) -> Primitive<'v> { + match self { + Cast::Primitive(value) => value, + #[cfg(feature = "std")] + _ => Primitive::None, + } + } +} + +impl<'v> Primitive<'v> { + fn into_borrowed_str(self) -> Option<&'v str> { + if let Primitive::Str(value) = self { + Some(value) + } else { + None + } + } + + fn into_u64(self) -> Option { + match self { + Primitive::Unsigned(value) => Some(value), + Primitive::Signed(value) => Some(value as u64), + Primitive::Float(value) => Some(value as u64), + _ => None, + } + } + + fn into_i64(self) -> Option { + match self { + Primitive::Signed(value) => Some(value), + Primitive::Unsigned(value) => Some(value as i64), + Primitive::Float(value) => Some(value as i64), + _ => None, + } + } + + fn into_f64(self) -> Option { + match self { + Primitive::Float(value) => Some(value), + Primitive::Unsigned(value) => Some(value as f64), + Primitive::Signed(value) => Some(value as f64), + _ => None, + } + } + + fn into_char(self) -> Option { + if let Primitive::Char(value) = self { + Some(value) + } else { + None + } + } + + fn into_bool(self) -> Option { + if let Primitive::Bool(value) = self { + Some(value) + } else { + None + } + } +} + +impl<'v, T: ?Sized + 'static> Erased<'v, T> { + // NOTE: This function is a perfect candidate for memoization + // The outcome could be stored in a `Cell` + fn downcast_primitive(self) -> Option> { + macro_rules! type_ids { + ($($value:ident : $ty:ty => $cast:expr,)*) => {{ + struct TypeIds; + + impl TypeIds { + fn downcast_primitive<'v, T: ?Sized>(&self, value: Erased<'v, T>) -> Option> { + $( + if TypeId::of::<$ty>() == value.type_id { + let $value = unsafe { value.downcast_unchecked::<$ty>() }; + return Some(Primitive::from($cast)); + } + )* + + None + } + } + + TypeIds + }}; + } + + let type_ids = type_ids![ + value: usize => *value as u64, + value: u8 => *value as u64, + value: u16 => *value as u64, + value: u32 => *value as u64, + value: u64 => *value, + + value: isize => *value as i64, + value: i8 => *value as i64, + value: i16 => *value as i64, + value: i32 => *value as i64, + value: i64 => *value, + + value: f32 => *value as f64, + value: f64 => *value, + + value: char => *value, + value: bool => *value, + + value: &str => *value, + ]; + + type_ids.downcast_primitive(self) + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + + use std::borrow::Cow; + + impl<'v> Value<'v> { + /// Try get a `usize` from this value. + /// + /// This method is cheap for primitive types, but may call arbitrary + /// serialization implementations for complex ones. If the serialization + /// implementation produces a short lived string it will be allocated. + pub fn to_str(&self) -> Option> { + self.inner.cast().into_str() + } + } + + impl<'v> Cast<'v> { + pub(super) fn into_str(self) -> Option> { + match self { + Cast::Primitive(Primitive::Str(value)) => Some(value.into()), + Cast::String(value) => Some(value.into()), + _ => None, + } + } + } + + #[cfg(test)] + mod tests { + use crate::kv::ToValue; + + #[test] + fn primitive_cast() { + assert_eq!( + "a string", + "a string" + .to_owned() + .to_value() + .to_borrowed_str() + .expect("invalid value") + ); + assert_eq!( + "a string", + &*"a string".to_value().to_str().expect("invalid value") + ); + assert_eq!( + "a string", + &*"a string" + .to_owned() + .to_value() + .to_str() + .expect("invalid value") + ); + } + } +} + +#[cfg(test)] +mod tests { + use crate::kv::ToValue; + + #[test] + fn primitive_cast() { + assert_eq!( + "a string", + "a string" + .to_value() + .to_borrowed_str() + .expect("invalid value") + ); + assert_eq!( + "a string", + Some("a string") + .to_value() + .to_borrowed_str() + .expect("invalid value") + ); + + assert_eq!(1u8, 1u64.to_value().to_u8().expect("invalid value")); + assert_eq!(1u16, 1u64.to_value().to_u16().expect("invalid value")); + assert_eq!(1u32, 1u64.to_value().to_u32().expect("invalid value")); + assert_eq!(1u64, 1u64.to_value().to_u64().expect("invalid value")); + assert_eq!(1usize, 1u64.to_value().to_usize().expect("invalid value")); + + assert_eq!(-1i8, -1i64.to_value().to_i8().expect("invalid value")); + assert_eq!(-1i16, -1i64.to_value().to_i16().expect("invalid value")); + assert_eq!(-1i32, -1i64.to_value().to_i32().expect("invalid value")); + assert_eq!(-1i64, -1i64.to_value().to_i64().expect("invalid value")); + assert_eq!(-1isize, -1i64.to_value().to_isize().expect("invalid value")); + + assert!(1f32.to_value().to_f32().is_some(), "invalid value"); + assert!(1f64.to_value().to_f64().is_some(), "invalid value"); + + assert_eq!(1u32, 1i64.to_value().to_u32().expect("invalid value")); + assert_eq!(1i32, 1u64.to_value().to_i32().expect("invalid value")); + assert!(1f32.to_value().to_i32().is_some(), "invalid value"); + + assert_eq!('a', 'a'.to_value().to_char().expect("invalid value")); + assert_eq!(true, true.to_value().to_bool().expect("invalid value")); + } +} diff --git a/src/kv/value/internal/coerce.rs b/src/kv/value/internal/coerce.rs deleted file mode 100644 index 102489bb3..000000000 --- a/src/kv/value/internal/coerce.rs +++ /dev/null @@ -1,334 +0,0 @@ -//! Coerce a `Value` into some concrete types. -//! -//! These operations are cheap when the captured value is a simple primitive, -//! but may end up executing arbitrary caller code if the value is complex. - -use std::fmt; - -use super::{Inner, Primitive, Visitor}; -use crate::kv; -use crate::kv::value::Error; - -impl<'v> kv::Value<'v> { - /// Try coerce the value into a borrowed string. - pub fn get_str(&self) -> Option<&str> { - self.inner.coerce().into_primitive().into_str() - } - - /// Try coerce the value into a `u8`. - pub fn get_u8(&self) -> Option { - self.inner - .coerce() - .into_primitive() - .into_u64() - .map(|v| v as u8) - } - - /// Try coerce the value into a `u16`. - pub fn get_u16(&self) -> Option { - self.inner - .coerce() - .into_primitive() - .into_u64() - .map(|v| v as u16) - } - - /// Try coerce the value into a `u32`. - pub fn get_u32(&self) -> Option { - self.inner - .coerce() - .into_primitive() - .into_u64() - .map(|v| v as u32) - } - - /// Try coerce the value into a `u64`. - pub fn get_u64(&self) -> Option { - self.inner.coerce().into_primitive().into_u64() - } - - /// Try coerce the value into a `i8`. - pub fn get_i8(&self) -> Option { - self.inner - .coerce() - .into_primitive() - .into_i64() - .map(|v| v as i8) - } - - /// Try coerce the value into a `i16`. - pub fn get_i16(&self) -> Option { - self.inner - .coerce() - .into_primitive() - .into_i64() - .map(|v| v as i16) - } - - /// Try coerce the value into a `i32`. - pub fn get_i32(&self) -> Option { - self.inner - .coerce() - .into_primitive() - .into_i64() - .map(|v| v as i32) - } - - /// Try coerce the value into a `i64`. - pub fn get_i64(&self) -> Option { - self.inner.coerce().into_primitive().into_i64() - } - - /// Try coerce the value into a `f32`. - pub fn get_f32(&self) -> Option { - self.inner - .coerce() - .into_primitive() - .into_f64() - .map(|v| v as f32) - } - - /// Try coerce the value into a `f64`. - pub fn get_f64(&self) -> Option { - self.inner.coerce().into_primitive().into_f64() - } - - /// Try coerce the value into a `char`. - pub fn get_char(&self) -> Option { - self.inner.coerce().into_primitive().into_char() - } - - /// Try coerce the value into a `bool`. - pub fn get_bool(&self) -> Option { - self.inner.coerce().into_primitive().into_bool() - } -} - -impl<'v> Inner<'v> { - fn coerce(&self) -> Coerced { - struct Coerce<'v>(Coerced<'v>); - - impl<'v> Coerce<'v> { - fn new() -> Self { - Coerce(Coerced::Primitive(Primitive::None)) - } - } - - impl<'v> Visitor<'v> for Coerce<'v> { - fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> { - Ok(()) - } - - fn u64(&mut self, v: u64) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Unsigned(v)); - Ok(()) - } - - fn i64(&mut self, v: i64) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Signed(v)); - Ok(()) - } - - fn f64(&mut self, v: f64) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Float(v)); - Ok(()) - } - - fn bool(&mut self, v: bool) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Bool(v)); - Ok(()) - } - - fn char(&mut self, v: char) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Char(v)); - Ok(()) - } - - fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::Str(v)); - Ok(()) - } - - #[cfg(not(feature = "std"))] - fn str(&mut self, _: &str) -> Result<(), Error> { - Ok(()) - } - - #[cfg(feature = "std")] - fn str(&mut self, v: &str) -> Result<(), Error> { - self.0 = Coerced::String(v.into()); - Ok(()) - } - - fn none(&mut self) -> Result<(), Error> { - self.0 = Coerced::Primitive(Primitive::None); - Ok(()) - } - - #[cfg(feature = "kv_unstable_sval")] - fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> { - self.0 = super::sval::coerce(v); - Ok(()) - } - } - - let mut coerce = Coerce::new(); - let _ = self.visit(&mut coerce); - coerce.0 - } -} - -pub(super) enum Coerced<'v> { - Primitive(Primitive<'v>), - #[cfg(feature = "std")] - String(String), -} - -impl<'v> Coerced<'v> { - fn into_primitive(self) -> Primitive<'v> { - match self { - Coerced::Primitive(value) => value, - #[cfg(feature = "std")] - _ => Primitive::None, - } - } -} - -impl<'v> Primitive<'v> { - fn into_str(self) -> Option<&'v str> { - if let Primitive::Str(value) = self { - Some(value) - } else { - None - } - } - - fn into_u64(self) -> Option { - if let Primitive::Unsigned(value) = self { - Some(value) - } else { - None - } - } - - fn into_i64(self) -> Option { - if let Primitive::Signed(value) = self { - Some(value) - } else { - None - } - } - - fn into_f64(self) -> Option { - if let Primitive::Float(value) = self { - Some(value) - } else { - None - } - } - - fn into_char(self) -> Option { - if let Primitive::Char(value) = self { - Some(value) - } else { - None - } - } - - fn into_bool(self) -> Option { - if let Primitive::Bool(value) = self { - Some(value) - } else { - None - } - } -} - -#[cfg(feature = "std")] -mod std_support { - use super::*; - - use std::borrow::Cow; - - impl<'v> kv::Value<'v> { - /// Try coerce the value into an owned or borrowed string. - pub fn get_string(&self) -> Option> { - self.inner.coerce().into_string() - } - } - - impl<'v> Coerced<'v> { - pub(super) fn into_string(self) -> Option> { - match self { - Coerced::Primitive(Primitive::Str(value)) => Some(value.into()), - Coerced::String(value) => Some(value.into()), - _ => None, - } - } - } - - #[cfg(test)] - mod tests { - use crate::kv::ToValue; - - #[test] - fn primtive_coercion() { - assert_eq!( - "a string", - "a string" - .to_owned() - .to_value() - .get_str() - .expect("invalid value") - ); - assert_eq!( - "a string", - &*"a string".to_value().get_string().expect("invalid value") - ); - assert_eq!( - "a string", - &*"a string" - .to_owned() - .to_value() - .get_string() - .expect("invalid value") - ); - } - } -} - -#[cfg(test)] -mod tests { - use crate::kv::ToValue; - - #[test] - fn primitive_coercion() { - assert_eq!( - "a string", - "a string".to_value().get_str().expect("invalid value") - ); - assert_eq!( - "a string", - Some("a string") - .to_value() - .get_str() - .expect("invalid value") - ); - - assert_eq!(1u8, 1u64.to_value().get_u8().expect("invalid value")); - assert_eq!(1u16, 1u64.to_value().get_u16().expect("invalid value")); - assert_eq!(1u32, 1u64.to_value().get_u32().expect("invalid value")); - assert_eq!(1u64, 1u64.to_value().get_u64().expect("invalid value")); - - assert_eq!(-1i8, -1i64.to_value().get_i8().expect("invalid value")); - assert_eq!(-1i16, -1i64.to_value().get_i16().expect("invalid value")); - assert_eq!(-1i32, -1i64.to_value().get_i32().expect("invalid value")); - assert_eq!(-1i64, -1i64.to_value().get_i64().expect("invalid value")); - - assert!(1f32.to_value().get_f32().is_some(), "invalid value"); - assert!(1f64.to_value().get_f64().is_some(), "invalid value"); - - assert_eq!('a', 'a'.to_value().get_char().expect("invalid value")); - assert_eq!(true, true.to_value().get_bool().expect("invalid value")); - } -} diff --git a/src/kv/value/internal/fmt.rs b/src/kv/value/internal/fmt.rs index 4f4a019c9..f8b92a3a1 100644 --- a/src/kv/value/internal/fmt.rs +++ b/src/kv/value/internal/fmt.rs @@ -5,7 +5,7 @@ use std::fmt; -use super::{Inner, Visitor}; +use super::{Erased, Inner, Visitor}; use crate::kv; use crate::kv::value::{Error, Slot}; @@ -13,20 +13,20 @@ impl<'v> kv::Value<'v> { /// Get a value from a debuggable type. pub fn from_debug(value: &'v T) -> Self where - T: fmt::Debug, + T: fmt::Debug + 'static, { kv::Value { - inner: Inner::Debug(value), + inner: Inner::Debug(unsafe { Erased::new_unchecked::(value) }), } } /// Get a value from a displayable type. pub fn from_display(value: &'v T) -> Self where - T: fmt::Display, + T: fmt::Display + 'static, { kv::Value { - inner: Inner::Display(value), + inner: Inner::Display(unsafe { Erased::new_unchecked::(value) }), } } } @@ -61,7 +61,7 @@ impl<'s, 'f> Slot<'s, 'f> { } } -pub(in kv::value) use self::fmt::{Debug, Display}; +pub(in kv::value) use self::fmt::{Arguments, Debug, Display}; impl<'v> fmt::Debug for kv::Value<'v> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -121,3 +121,25 @@ impl<'a, 'b: 'a, 'v> Visitor<'v> for FmtVisitor<'a, 'b> { super::sval::fmt(self.0, v) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fmt_cast() { + assert_eq!( + 42u32, + kv::Value::from_debug(&42u64) + .to_u32() + .expect("invalid value") + ); + + assert_eq!( + "a string", + kv::Value::from_display(&"a string") + .to_borrowed_str() + .expect("invalid value") + ); + } +} diff --git a/src/kv/value/internal/mod.rs b/src/kv/value/internal/mod.rs index aa108dffe..429f0db98 100644 --- a/src/kv/value/internal/mod.rs +++ b/src/kv/value/internal/mod.rs @@ -3,9 +3,11 @@ //! This implementation isn't intended to be public. It may need to change //! for optimizations or to support new external serialization frameworks. +use std::any::TypeId; + use super::{Error, Fill, Slot}; -pub(super) mod coerce; +pub(super) mod cast; pub(super) mod fmt; #[cfg(feature = "kv_unstable_sval")] pub(super) mod sval; @@ -16,27 +18,27 @@ pub(super) enum Inner<'v> { /// A simple primitive value that can be copied without allocating. Primitive(Primitive<'v>), /// A value that can be filled. - Fill(&'v dyn Fill), + Fill(Erased<'v, dyn Fill + 'static>), /// A debuggable value. - Debug(&'v dyn fmt::Debug), + Debug(Erased<'v, dyn fmt::Debug + 'static>), /// A displayable value. - Display(&'v dyn fmt::Display), + Display(Erased<'v, dyn fmt::Display + 'static>), #[cfg(feature = "kv_unstable_sval")] /// A structured value from `sval`. - Sval(&'v dyn sval::Value), + Sval(Erased<'v, dyn sval::Value + 'static>), } impl<'v> Inner<'v> { pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { match self { Inner::Primitive(value) => value.visit(visitor), - Inner::Fill(value) => value.fill(&mut Slot::new(visitor)), - Inner::Debug(value) => visitor.debug(value), - Inner::Display(value) => visitor.display(value), + Inner::Fill(value) => value.get().fill(&mut Slot::new(visitor)), + Inner::Debug(value) => visitor.debug(value.get()), + Inner::Display(value) => visitor.display(value.get()), #[cfg(feature = "kv_unstable_sval")] - Inner::Sval(value) => visitor.sval(value), + Inner::Sval(value) => visitor.sval(value.get()), } } } @@ -76,6 +78,7 @@ pub(super) enum Primitive<'v> { Bool(bool), Char(char), Str(&'v str), + Fmt(fmt::Arguments<'v>), None, } @@ -88,7 +91,91 @@ impl<'v> Primitive<'v> { Primitive::Bool(value) => visitor.bool(value), Primitive::Char(value) => visitor.char(value), Primitive::Str(value) => visitor.borrowed_str(value), + Primitive::Fmt(value) => visitor.debug(&value), Primitive::None => visitor.none(), } } } + +impl<'v> From for Primitive<'v> { + fn from(v: u64) -> Self { + Primitive::Unsigned(v) + } +} + +impl<'v> From for Primitive<'v> { + fn from(v: i64) -> Self { + Primitive::Signed(v) + } +} + +impl<'v> From for Primitive<'v> { + fn from(v: f64) -> Self { + Primitive::Float(v) + } +} + +impl<'v> From for Primitive<'v> { + fn from(v: bool) -> Self { + Primitive::Bool(v) + } +} + +impl<'v> From for Primitive<'v> { + fn from(v: char) -> Self { + Primitive::Char(v) + } +} + +impl<'v> From<&'v str> for Primitive<'v> { + fn from(v: &'v str) -> Self { + Primitive::Str(v) + } +} + +impl<'v> From> for Primitive<'v> { + fn from(v: fmt::Arguments<'v>) -> Self { + Primitive::Fmt(v) + } +} + +/// A downcastable dynamic type. +pub(super) struct Erased<'v, T: ?Sized> { + type_id: TypeId, + inner: &'v T, +} + +impl<'v, T: ?Sized> Clone for Erased<'v, T> { + fn clone(&self) -> Self { + Erased { + type_id: self.type_id, + inner: self.inner, + } + } +} + +impl<'v, T: ?Sized> Copy for Erased<'v, T> {} + +impl<'v, T: ?Sized> Erased<'v, T> { + // SAFETY: `U: Unsize` and the underlying value `T` must not change + // We could add a safe variant of this method with the `Unsize` trait + pub(super) unsafe fn new_unchecked(inner: &'v T) -> Self + where + U: 'static, + T: 'static, + { + Erased { + type_id: TypeId::of::(), + inner, + } + } + + pub(super) fn get(self) -> &'v T { + self.inner + } + + // SAFETY: The underlying type of `T` is `U` + pub(super) unsafe fn downcast_unchecked(self) -> &'v U { + &*(self.inner as *const T as *const U) + } +} diff --git a/src/kv/value/internal/sval.rs b/src/kv/value/internal/sval.rs index ccd9f6454..12d184290 100644 --- a/src/kv/value/internal/sval.rs +++ b/src/kv/value/internal/sval.rs @@ -7,8 +7,8 @@ extern crate sval; use std::fmt; -use super::coerce::Coerced; -use super::{Inner, Primitive, Visitor}; +use super::cast::Cast; +use super::{Erased, Inner, Primitive, Visitor}; use crate::kv; use crate::kv::value::{Error, Slot}; @@ -16,10 +16,10 @@ impl<'v> kv::Value<'v> { /// Get a value from a structured type. pub fn from_sval(value: &'v T) -> Self where - T: sval::Value, + T: sval::Value + 'static, { kv::Value { - inner: Inner::Sval(value), + inner: Inner::Sval(unsafe { Erased::new_unchecked::(value) }), } } } @@ -55,46 +55,46 @@ pub(super) fn fmt(f: &mut fmt::Formatter, v: &dyn sval::Value) -> Result<(), Err Ok(()) } -pub(super) fn coerce<'v>(v: &dyn sval::Value) -> Coerced<'v> { - struct Coerce<'v>(Coerced<'v>); +pub(super) fn cast<'v>(v: &dyn sval::Value) -> Cast<'v> { + struct CastStream<'v>(Cast<'v>); - impl<'v> sval::Stream for Coerce<'v> { + impl<'v> sval::Stream for CastStream<'v> { fn u64(&mut self, v: u64) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Unsigned(v)); + self.0 = Cast::Primitive(Primitive::Unsigned(v)); Ok(()) } fn i64(&mut self, v: i64) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Signed(v)); + self.0 = Cast::Primitive(Primitive::Signed(v)); Ok(()) } fn f64(&mut self, v: f64) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Float(v)); + self.0 = Cast::Primitive(Primitive::Float(v)); Ok(()) } fn char(&mut self, v: char) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Char(v)); + self.0 = Cast::Primitive(Primitive::Char(v)); Ok(()) } fn bool(&mut self, v: bool) -> sval::stream::Result { - self.0 = Coerced::Primitive(Primitive::Bool(v)); + self.0 = Cast::Primitive(Primitive::Bool(v)); Ok(()) } #[cfg(feature = "std")] fn str(&mut self, s: &str) -> sval::stream::Result { - self.0 = Coerced::String(s.into()); + self.0 = Cast::String(s.into()); Ok(()) } } - let mut coerce = Coerce(Coerced::Primitive(Primitive::None)); - let _ = sval::stream(&mut coerce, v); + let mut cast = CastStream(Cast::Primitive(Primitive::None)); + let _ = sval::stream(&mut cast, v); - coerce.0 + cast.0 } impl Error { @@ -168,21 +168,26 @@ mod tests { } #[test] - fn sval_coersion() { + fn sval_cast() { assert_eq!( - 42u64, + 42u32, kv::Value::from_sval(&42u64) - .get_u64() + .to_u32() .expect("invalid value") ); - assert!(kv::Value::from_sval(&"a string").get_str().is_none()); + assert_eq!( + "a string", + kv::Value::from_sval(&"a string") + .to_borrowed_str() + .expect("invalid value") + ); #[cfg(feature = "std")] assert_eq!( "a string", - &*kv::Value::from_sval(&"a string") - .get_string() + kv::Value::from_sval(&"a string") + .to_str() .expect("invalid value") ); } diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs index ce609a6ea..422adb0b5 100644 --- a/src/kv/value/mod.rs +++ b/src/kv/value/mod.rs @@ -40,9 +40,12 @@ pub struct Value<'v> { impl<'v> Value<'v> { /// Get a value from an internal primitive. - fn from_primitive(value: Primitive<'v>) -> Self { + fn from_primitive(value: T) -> Self + where + T: Into>, + { Value { - inner: Inner::Primitive(value), + inner: Inner::Primitive(value.into()), } } diff --git a/src/lib.rs b/src/lib.rs index b711146f4..ac4243e7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1663,7 +1663,7 @@ mod tests { .key_values() .get("b".into()) .expect("missing key") - .get_str() + .to_borrowed_str() .expect("invalid value") ); }