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/fill.rs b/src/kv/value/fill.rs new file mode 100644 index 000000000..9642132f4 --- /dev/null +++ b/src/kv/value/fill.rs @@ -0,0 +1,148 @@ +//! Lazy value initialization. + +use std::fmt; + +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 + 'static, + { + Value { + inner: Inner::Fill(unsafe { Erased::new_unchecked::(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_cast() { + 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) + .to_borrowed_str() + .expect("invalid value") + ); + } +} diff --git a/src/kv/value/impls.rs b/src/kv/value/impls.rs index 59b181e37..23e8b1bed 100644 --- a/src/kv/value/impls.rs +++ b/src/kv/value/impls.rs @@ -1,184 +1,51 @@ +//! 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}; -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) - } -} +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 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)) - } + 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) } } @@ -200,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.rs b/src/kv/value/internal.rs deleted file mode 100644 index f2c22a510..000000000 --- a/src/kv/value/internal.rs +++ /dev/null @@ -1,266 +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) -> 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(), - }, - 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 { - 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 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, -} - -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<'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> Visitor 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::*; - - 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<'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> Visitor 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); - } - } -} 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/fmt.rs b/src/kv/value/internal/fmt.rs new file mode 100644 index 000000000..f8b92a3a1 --- /dev/null +++ b/src/kv/value/internal/fmt.rs @@ -0,0 +1,145 @@ +//! 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::{Erased, 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 + 'static, + { + kv::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 + 'static, + { + kv::Value { + inner: Inner::Display(unsafe { Erased::new_unchecked::(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::{Arguments, 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) + } +} + +#[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 new file mode 100644 index 000000000..429f0db98 --- /dev/null +++ b/src/kv/value/internal/mod.rs @@ -0,0 +1,181 @@ +//! 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 std::any::TypeId; + +use super::{Error, Fill, Slot}; + +pub(super) mod cast; +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(Erased<'v, dyn Fill + 'static>), + /// A debuggable value. + Debug(Erased<'v, dyn fmt::Debug + 'static>), + /// A displayable value. + Display(Erased<'v, dyn fmt::Display + 'static>), + + #[cfg(feature = "kv_unstable_sval")] + /// A structured value from `sval`. + 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.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.get()), + } + } +} + +/// 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), + Fmt(fmt::Arguments<'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::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 new file mode 100644 index 000000000..12d184290 --- /dev/null +++ b/src/kv/value/internal/sval.rs @@ -0,0 +1,194 @@ +//! 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::cast::Cast; +use super::{Erased, 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 + 'static, + { + kv::Value { + inner: Inner::Sval(unsafe { Erased::new_unchecked::(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 cast<'v>(v: &dyn sval::Value) -> Cast<'v> { + struct CastStream<'v>(Cast<'v>); + + impl<'v> sval::Stream for CastStream<'v> { + fn u64(&mut self, v: u64) -> sval::stream::Result { + self.0 = Cast::Primitive(Primitive::Unsigned(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> sval::stream::Result { + self.0 = Cast::Primitive(Primitive::Signed(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> sval::stream::Result { + self.0 = Cast::Primitive(Primitive::Float(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> sval::stream::Result { + self.0 = Cast::Primitive(Primitive::Char(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> sval::stream::Result { + self.0 = Cast::Primitive(Primitive::Bool(v)); + Ok(()) + } + + #[cfg(feature = "std")] + fn str(&mut self, s: &str) -> sval::stream::Result { + self.0 = Cast::String(s.into()); + Ok(()) + } + } + + let mut cast = CastStream(Cast::Primitive(Primitive::None)); + let _ = sval::stream(&mut cast, v); + + cast.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_cast() { + assert_eq!( + 42u32, + kv::Value::from_sval(&42u64) + .to_u32() + .expect("invalid value") + ); + + 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") + .to_str() + .expect("invalid value") + ); + } +} diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs index 332170d33..422adb0b5 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,121 +33,24 @@ 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<'a> { - filled: bool, - visitor: &'a mut dyn Visitor, -} - -impl<'a> fmt::Debug for Slot<'a> { - 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 { - Slot { - visitor, - filled: false, - } - } - - /// 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) - } -} - /// 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`. - 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 + /// Get a value from an internal primitive. + fn from_primitive(value: T) -> Self where - T: Fill, + T: Into>, { Value { - inner: Inner::Fill(value), + inner: Inner::Primitive(value.into()), } } - fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> { + /// Visit the value using an internal visitor. + fn visit<'a>(&'a self, visitor: &mut dyn Visitor<'a>) -> Result<(), Error> { self.inner.visit(visitor) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fill_value() { - 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)) - } - } - - assert_eq!("1", Value::from_fill(&TestFill).to_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(42.into())?; - slot.fill(6789.into())?; - - Ok(()) - } - } - - let _ = Value::from_fill(&BadFill).to_string(); - } -} diff --git a/src/kv/value/test.rs b/src/kv/value/test.rs index f57715b9d..ab5f8075e 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(()) @@ -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(()) } diff --git a/src/lib.rs b/src/lib.rs index f13e4a8d7..ac4243e7a 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(), @@ -1649,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, &str)] = &[("a", "1"), ("b", "2")]; + let record = Record::builder().key_values(&kvs).build(); + + assert_eq!( + "2", + record + .key_values() + .get("b".into()) + .expect("missing key") + .to_borrowed_str() + .expect("invalid value") + ); + } }