Merge pull request #379 from KodrAus/feat/value-coercion

Support coercing structured Values into primitive types
This commit is contained in:
Ashley Mannix 2020-02-03 09:43:11 +10:00 committed by GitHub
commit d54317e47a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1226 additions and 547 deletions

View File

@ -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"] }

148
src/kv/value/fill.rs Normal file
View File

@ -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<T>(value: &'v T) -> Self
where
T: Fill + 'static,
{
Value {
inner: Inner::Fill(unsafe { Erased::new_unchecked::<T>(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<F>(&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<T>(&mut self, value: T) -> Result<(), Error>
where
T: Into<Value<'f>>,
{
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")
);
}
}

View File

@ -1,173 +1,28 @@
//! 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)
}
}
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<usize> 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<isize> 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<u8> 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<u16> 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<u32> 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<u64> 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<i8> 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<i16> 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<i32> 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<i64> 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<f32> 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<f64> 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<bool> for Value<'v> {
fn from(value: bool) -> Self {
Value::from_primitive(Primitive::Bool(value))
}
}
impl ToValue for char {
fn to_value(&self) -> Value {
Value::from(*self)
}
}
impl<'v> From<char> for Value<'v> {
fn from(value: char) -> Self {
Value::from_primitive(Primitive::Char(value))
}
impl<'v> From<$into_ty> for Value<'v> {
fn from(value: $into_ty) -> Self {
Value::from_primitive(value as $convert)
}
}
)*
};
}
impl<'v> ToValue for &'v str {
@ -178,7 +33,19 @@ impl<'v> ToValue for &'v str {
impl<'v> From<&'v str> for Value<'v> {
fn from(value: &'v str) -> Self {
Value::from_primitive(Primitive::Str(value))
Value::from_primitive(value)
}
}
impl<'v> ToValue for fmt::Arguments<'v> {
fn to_value(&self) -> Value {
Value::from(*self)
}
}
impl<'v> From<fmt::Arguments<'v>> 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 {

View File

@ -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<T>(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<T>(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<T>(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);
}
}
}

View File

@ -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<usize> {
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<u8> {
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<u16> {
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<u32> {
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<u64> {
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<isize> {
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<i8> {
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<i16> {
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<i32> {
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<i64> {
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<f32> {
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<f64> {
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<bool> {
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<char> {
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<u64> {
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<i64> {
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<f64> {
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<char> {
if let Primitive::Char(value) = self {
Some(value)
} else {
None
}
}
fn into_bool(self) -> Option<bool> {
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<Primitive>`
fn downcast_primitive(self) -> Option<Primitive<'v>> {
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<Primitive<'v>> {
$(
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<Cow<str>> {
self.inner.cast().into_str()
}
}
impl<'v> Cast<'v> {
pub(super) fn into_str(self) -> Option<Cow<'v, str>> {
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"));
}
}

View File

@ -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<T>(value: &'v T) -> Self
where
T: fmt::Debug + 'static,
{
kv::Value {
inner: Inner::Debug(unsafe { Erased::new_unchecked::<T>(value) }),
}
}
/// Get a value from a displayable type.
pub fn from_display<T>(value: &'v T) -> Self
where
T: fmt::Display + 'static,
{
kv::Value {
inner: Inner::Display(unsafe { Erased::new_unchecked::<T>(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<T>(&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<T>(&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")
);
}
}

View File

@ -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<u64> for Primitive<'v> {
fn from(v: u64) -> Self {
Primitive::Unsigned(v)
}
}
impl<'v> From<i64> for Primitive<'v> {
fn from(v: i64) -> Self {
Primitive::Signed(v)
}
}
impl<'v> From<f64> for Primitive<'v> {
fn from(v: f64) -> Self {
Primitive::Float(v)
}
}
impl<'v> From<bool> for Primitive<'v> {
fn from(v: bool) -> Self {
Primitive::Bool(v)
}
}
impl<'v> From<char> 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<fmt::Arguments<'v>> 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<T>` 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<U>(inner: &'v T) -> Self
where
U: 'static,
T: 'static,
{
Erased {
type_id: TypeId::of::<U>(),
inner,
}
}
pub(super) fn get(self) -> &'v T {
self.inner
}
// SAFETY: The underlying type of `T` is `U`
pub(super) unsafe fn downcast_unchecked<U>(self) -> &'v U {
&*(self.inner as *const T as *const U)
}
}

View File

@ -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<T>(value: &'v T) -> Self
where
T: sval::Value + 'static,
{
kv::Value {
inner: Inner::Sval(unsafe { Erased::new_unchecked::<T>(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<T>(&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")
);
}
}

View File

@ -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<T>(value: &'v T) -> Self
/// Get a value from an internal primitive.
fn from_primitive<T>(value: T) -> Self
where
T: Fill,
T: Into<Primitive<'v>>,
{
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();
}
}

View File

@ -25,7 +25,7 @@ impl<'v> Value<'v> {
pub(in kv) fn to_token(&self) -> Token {
struct TestVisitor(Option<Token>);
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(())
}

View File

@ -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")
);
}
}