From 58b19a3b0f3e73845d7477fa4bccbfa232d9207f Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Wed, 13 Mar 2019 15:16:31 +1100 Subject: [PATCH] build out innitial key values API --- .travis.yml | 6 + Cargo.toml | 3 + src/key_values/error.rs | 50 ++++++ src/key_values/key.rs | 111 ++++++++++++ src/key_values/mod.rs | 11 ++ src/key_values/source.rs | 116 +++++++++++++ src/key_values/value/any.rs | 94 +++++++++++ src/key_values/value/backend.rs | 62 +++++++ src/key_values/value/impls.rs | 287 ++++++++++++++++++++++++++++++++ src/key_values/value/mod.rs | 73 ++++++++ src/lib.rs | 120 ++++++++++++- 11 files changed, 931 insertions(+), 2 deletions(-) create mode 100644 src/key_values/error.rs create mode 100644 src/key_values/key.rs create mode 100644 src/key_values/mod.rs create mode 100644 src/key_values/source.rs create mode 100644 src/key_values/value/any.rs create mode 100644 src/key_values/value/backend.rs create mode 100644 src/key_values/value/impls.rs create mode 100644 src/key_values/value/mod.rs diff --git a/.travis.yml b/.travis.yml index 6780b26a9..df8afebb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,12 @@ rust: - stable - beta - nightly +matrix: + include: + - rust: 1.21.0 + script: + - cargo test --verbose --features kv_unstable + - cargo test --verbose --features "kv_unstable std" script: - cargo build --verbose - cargo build --verbose --features serde diff --git a/Cargo.toml b/Cargo.toml index 5b1d59cb3..ad3f4affa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,9 @@ release_max_level_trace = [] std = [] +# requires Rust `>= 1.21.0` +kv_unstable = [] + [badges] travis-ci = { repository = "rust-lang-nursery/log" } appveyor = { repository = "alexcrichton/log" } diff --git a/src/key_values/error.rs b/src/key_values/error.rs new file mode 100644 index 000000000..e45a7a82e --- /dev/null +++ b/src/key_values/error.rs @@ -0,0 +1,50 @@ +use std::fmt; + +/// An error encountered while working with structured data. +#[derive(Clone, Debug)] +pub struct KeyValueError { + msg: &'static str, +} + +impl KeyValueError { + /// Create an error from the given message. + pub fn msg(msg: &'static str) -> Self { + KeyValueError { + msg: msg, + } + } +} + +impl fmt::Display for KeyValueError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.msg.fmt(f) + } +} + +impl From for KeyValueError { + fn from(_: fmt::Error) -> Self { + KeyValueError::msg("formatting failed") + } +} + +impl From for fmt::Error { + fn from(_: KeyValueError) -> Self { + fmt::Error + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + use std::error; + + impl error::Error for KeyValueError { + fn description(&self) -> &str { + self.msg + } + + fn cause(&self) -> Option<&error::Error> { + None + } + } +} \ No newline at end of file diff --git a/src/key_values/key.rs b/src/key_values/key.rs new file mode 100644 index 000000000..7e75cb218 --- /dev/null +++ b/src/key_values/key.rs @@ -0,0 +1,111 @@ +//! Structured keys. + +use std::fmt; +use std::borrow::Borrow; + +/// A type that can be converted into a [`Key`](struct.Key.html). +pub trait ToKey { + /// Perform the covnersion. + fn to_key(&self) -> Key; +} + +impl<'a, T> ToKey for &'a T +where + T: ToKey + ?Sized, +{ + fn to_key(&self) -> Key { + (**self).to_key() + } +} + +impl<'k> ToKey for Key<'k> { + fn to_key(&self) -> Key { + Key { + key: self.key, + } + } +} + +impl<'k> ToKey for &'k str { + fn to_key(&self) -> Key { + Key::from_str(self) + } +} + +/// A key in a structured key-value pair. +pub struct Key<'k> { + key: &'k str, +} + +impl<'k> Key<'k> { + /// Get a key from a borrowed string. + pub fn from_str(key: &'k str) -> Self { + Key { + key: key, + } + } + + /// Get a borrowed string from this key. + pub fn as_str(&self) -> &str { + self.key + } +} + +impl<'k> fmt::Debug for Key<'k> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.key.fmt(f) + } +} + +impl<'k> fmt::Display for Key<'k> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.key.fmt(f) + } +} + +impl<'k> AsRef for Key<'k> { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl<'k> Borrow for Key<'k> { + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl<'k> From<&'k str> for Key<'k> { + fn from(s: &'k str) -> Self { + Key::from_str(s) + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + + use std::borrow::Cow; + + impl ToKey for String { + fn to_key(&self) -> Key { + Key::from_str(self) + } + } + + impl<'a> ToKey for Cow<'a, str> { + fn to_key(&self) -> Key { + Key::from_str(self) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn key_from_string() { + assert_eq!("a key", Key::from_str("a key").as_str()); + } +} \ No newline at end of file diff --git a/src/key_values/mod.rs b/src/key_values/mod.rs new file mode 100644 index 000000000..da8505776 --- /dev/null +++ b/src/key_values/mod.rs @@ -0,0 +1,11 @@ +//! Structured key-value pairs. + +mod error; +pub mod source; +pub mod key; +pub mod value; + +pub use self::error::KeyValueError; +pub use self::source::Source; +pub use self::key::Key; +pub use self::value::Value; diff --git a/src/key_values/source.rs b/src/key_values/source.rs new file mode 100644 index 000000000..b0a49ac28 --- /dev/null +++ b/src/key_values/source.rs @@ -0,0 +1,116 @@ +//! Sources for key-value pairs. + +pub use key_values::{KeyValueError, Key, Value}; + +use key_values::key::ToKey; +use key_values::value::ToValue; + +/// A source of key-value pairs. +/// +/// The source may be a single pair, a set of pairs, or a filter over a set of pairs. +/// Use the [`Visitor`](struct.Visitor.html) trait to inspect the structured data +/// in a source. +pub trait Source { + /// Visit key-value pairs. + /// + /// A source doesn't have to guarantee any ordering or uniqueness of pairs. + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), KeyValueError>; +} + +impl<'a, T> Source for &'a T +where + T: Source + ?Sized, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), KeyValueError> { + (**self).visit(visitor) + } +} + +impl Source for (K, V) +where + K: ToKey, + V: ToValue, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), KeyValueError> { + visitor.visit_pair(self.0.to_key(), self.1.to_value()) + } +} + +impl Source for [S] +where + S: Source, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), KeyValueError> { + for source in self { + source.visit(visitor)?; + } + + Ok(()) + } +} + +impl Source for Option +where + S: Source, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), KeyValueError> { + if let Some(ref source) = *self { + source.visit(visitor)?; + } + + Ok(()) + } +} + +/// A visitor for the key-value pairs in a [`Source`](trait.Source.html). +pub trait Visitor<'kvs> { + /// Visit a key-value pair. + fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), KeyValueError>; +} + +impl<'a, 'kvs, T> Visitor<'kvs> for &'a mut T +where + T: Visitor<'kvs> + ?Sized, +{ + fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), KeyValueError> { + (**self).visit_pair(key, value) + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + + impl Source for Box + where + S: Source + ?Sized, + { + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), KeyValueError> { + (**self).visit(visitor) + } + } + + impl Source for Vec + where + S: Source, + { + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), KeyValueError> { + (**self).visit(visitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn source_is_object_safe() { + fn _check(_: &Source) {} + } + + #[test] + fn visitor_is_object_safe() { + fn _check(_: &Visitor) {} + } +} \ No newline at end of file diff --git a/src/key_values/value/any.rs b/src/key_values/value/any.rs new file mode 100644 index 000000000..79eafefe3 --- /dev/null +++ b/src/key_values/value/any.rs @@ -0,0 +1,94 @@ +use std::{fmt, mem}; +use std::marker::PhantomData; + +use super::{ToValue, KeyValueError}; +use super::backend::Backend; + +/// A function for converting some type `T` into a [`Value`](struct.Value.html). +pub type FromAnyFn = fn(FromAny, &T) -> Result<(), KeyValueError>; + +/// A helper for converting any type into a [`Value`](struct.Value.html). +pub struct FromAny<'a>(&'a mut Backend); + +impl<'a> FromAny<'a> { + pub(super) fn value(self, v: T) -> Result<(), KeyValueError> + where + T: ToValue, + { + v.to_value().inner.visit(self.0) + } + + /// Convert a formattable type into a value. + pub fn debug(self, v: T) -> Result<(), KeyValueError> + where + T: fmt::Debug + { + self.0.fmt(format_args!("{:?}", v)) + } + + /// Convert a `u64` into a value. + pub fn u64(self, v: u64) -> Result<(), KeyValueError> { + self.0.u64(v) + } + + /// Convert a `i64` into a value. + pub fn i64(self, v: i64) -> Result<(), KeyValueError> { + self.0.i64(v) + } + + /// Convert a `f64` into a value. + pub fn f64(self, v: f64) -> Result<(), KeyValueError> { + self.0.f64(v) + } + + /// Convert a `bool` into a value. + pub fn bool(self, v: bool) -> Result<(), KeyValueError> { + self.0.bool(v) + } + + /// Convert a `char` into a value. + pub fn char(self, v: char) -> Result<(), KeyValueError> { + self.0.char(v) + } + + /// Convert an empty type into a value. + pub fn none(self) -> Result<(), KeyValueError> { + self.0.none() + } + + /// Convert a string into a value. + pub fn str(self, v: &str) -> Result<(), KeyValueError> { + self.0.str(v) + } +} + +// `Any<'a>` is very similar to `std::fmt::Arguments<'a>` +// It takes a &T and fn pointer and stores them in an erased structure. +// It's a bit like an ad-hoc trait object that can accept any arbitrary +// value without those values needing to implement any traits. + +#[derive(Clone, Copy)] +pub(super) struct Any<'a> { + data: &'a Void, + from: FromAnyFn, +} + +struct Void { + _priv: (), + _oibit_remover: PhantomData<*mut Fn()>, +} + +impl<'a> Any<'a> { + pub(super) fn new(data: &'a T, from: FromAnyFn) -> Self { + unsafe { + Any { + data: mem::transmute::<&'a T, &'a Void>(data), + from: mem::transmute::, FromAnyFn>(from), + } + } + } + + pub(super) fn visit(&self, backend: &mut Backend) -> Result<(), KeyValueError> { + (self.from)(FromAny(backend), self.data) + } +} diff --git a/src/key_values/value/backend.rs b/src/key_values/value/backend.rs new file mode 100644 index 000000000..a378581dd --- /dev/null +++ b/src/key_values/value/backend.rs @@ -0,0 +1,62 @@ +use std::fmt; + +use super::KeyValueError; + +// `Backend` is an internal visitor for the structure of a value. +// Right now we only have an implementation for `std::fmt`, but +// this trait makes it possible to add more structured backends like +// `serde` that can retain some of that original structure. +// +// `Backend` isn't expected to be public, so that `log` can hide its +// internal serialization contract. This keeps its public API small +// and gives us room to move while the API is still evolving. +// For a public API, see the `FromAny` type. + +pub(super) trait Backend { + fn fmt(&mut self, v: fmt::Arguments) -> Result<(), KeyValueError>; + fn u64(&mut self, v: u64) -> Result<(), KeyValueError>; + fn i64(&mut self, v: i64) -> Result<(), KeyValueError>; + fn f64(&mut self, v: f64) -> Result<(), KeyValueError>; + fn bool(&mut self, v: bool) -> Result<(), KeyValueError>; + fn char(&mut self, v: char) -> Result<(), KeyValueError>; + fn str(&mut self, v: &str) -> Result<(), KeyValueError>; + fn none(&mut self) -> Result<(), KeyValueError>; +} + +pub(super) struct FmtBackend<'a, 'b: 'a>(pub(super) &'a mut fmt::Formatter<'b>); + +impl<'a, 'b: 'a> Backend for FmtBackend<'a, 'b> { + fn fmt(&mut self, v: fmt::Arguments) -> Result<(), KeyValueError> { + fmt::Debug::fmt(&v, self.0)?; + + Ok(()) + } + + fn u64(&mut self, v: u64) -> Result<(), KeyValueError> { + self.fmt(format_args!("{:?}", v)) + } + + fn i64(&mut self, v: i64) -> Result<(), KeyValueError> { + self.fmt(format_args!("{:?}", v)) + } + + fn f64(&mut self, v: f64) -> Result<(), KeyValueError> { + self.fmt(format_args!("{:?}", v)) + } + + fn bool(&mut self, v: bool) -> Result<(), KeyValueError> { + self.fmt(format_args!("{:?}", v)) + } + + fn char(&mut self, v: char) -> Result<(), KeyValueError> { + self.fmt(format_args!("{:?}", v)) + } + + fn str(&mut self, v: &str) -> Result<(), KeyValueError> { + self.fmt(format_args!("{:?}", v)) + } + + fn none(&mut self) -> Result<(), KeyValueError> { + self.fmt(format_args!("{:?}", Option::None::<()>)) + } +} diff --git a/src/key_values/value/impls.rs b/src/key_values/value/impls.rs new file mode 100644 index 000000000..c63b2346b --- /dev/null +++ b/src/key_values/value/impls.rs @@ -0,0 +1,287 @@ +use std::fmt; + +use super::{ToValue, Value}; + +impl ToValue for u8 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.u64(*value as u64)) + } +} + +impl ToValue for u16 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.u64(*value as u64)) + } +} + +impl ToValue for u32 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.u64(*value as u64)) + } +} + +impl ToValue for u64 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.u64(*value)) + } +} + +impl ToValue for i8 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.i64(*value as i64)) + } +} + +impl ToValue for i16 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.i64(*value as i64)) + } +} + +impl ToValue for i32 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.i64(*value as i64)) + } +} + +impl ToValue for i64 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.i64(*value)) + } +} + +impl ToValue for f32 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.f64(*value as f64)) + } +} + +impl ToValue for f64 { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.f64(*value)) + } +} + +impl ToValue for bool { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.bool(*value)) + } +} + +impl ToValue for char { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.char(*value)) + } +} + +impl<'v> ToValue for &'v str { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.str(value)) + } +} + +impl ToValue for () { + fn to_value(&self) -> Value { + Value::from_any(self, |from, _| from.none()) + } +} + +impl ToValue for Option +where + T: ToValue, +{ + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| { + match *value { + Some(ref value) => from.value(value), + None => from.none(), + } + }) + } +} + +impl<'v> ToValue for fmt::Arguments<'v> { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.debug(value)) + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + + use std::borrow::Cow; + + impl ToValue for Box + where + T: ToValue + ?Sized, + { + fn to_value(&self) -> Value { + (**self).to_value() + } + } + + impl ToValue for String { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.str(&*value)) + } + } + + impl<'a> ToValue for Cow<'a, str> { + fn to_value(&self) -> Value { + Value::from_any(self, |from, value| from.str(&*value)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use key_values::value::KeyValueError; + use key_values::value::backend::Backend; + + use std::fmt::Write; + use std::str::{self, Utf8Error}; + + // A quick-and-dirty no-std buffer + // to write strings into + struct Buffer { + buf: [u8; 16], + len: usize, + } + + impl Buffer { + fn new() -> Self { + Buffer { + buf: [0; 16], + len: 0, + } + } + + fn as_str(&self) -> Result<&str, Utf8Error> { + str::from_utf8(&self.buf[0..self.len]) + } + } + + impl Write for Buffer { + fn write_str(&mut self, s: &str) -> fmt::Result { + let bytes = s.as_bytes(); + + let end = self.len + bytes.len(); + + if end > 16 { + panic!("`{}` would overflow", s); + } + + let buf = &mut self.buf[self.len..end]; + buf.copy_from_slice(bytes); + self.len = end; + + Ok(()) + } + } + + #[test] + fn test_to_value_display() { + // Write a value into our buffer using `::fmt` + fn check(value: Value, expected: &str) { + let mut buf = Buffer::new(); + write!(&mut buf, "{}", value).unwrap(); + + assert_eq!(expected, buf.as_str().unwrap()); + } + + check(42u64.to_value(), "42"); + check(42i64.to_value(), "42"); + check(42.01f64.to_value(), "42.01"); + check(true.to_value(), "true"); + check('a'.to_value(), "'a'"); + check(format_args!("a {}", "value").to_value(), "a value"); + check("a loong string".to_value(), "\"a loong string\""); + check(Some(true).to_value(), "true"); + check(().to_value(), "None"); + check(Option::None::.to_value(), "None"); + } + + #[test] + fn test_to_value_structured() { + #[derive(Debug, PartialEq)] + enum Token<'a> { + U64(u64), + I64(i64), + F64(f64), + Char(char), + Bool(bool), + Str(&'a str), + None, + } + + struct TestBackend(F); + + impl Backend for TestBackend + where + F: Fn(Token), + { + fn fmt(&mut self, v: fmt::Arguments) -> Result<(), KeyValueError> { + let mut buf = Buffer::new(); + write!(&mut buf, "{}", v)?; + + let s = buf.as_str().map_err(|_| KeyValueError::msg("invalid UTF8"))?; + (self.0)(Token::Str(s)); + Ok(()) + } + + fn u64(&mut self, v: u64) -> Result<(), KeyValueError> { + (self.0)(Token::U64(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> Result<(), KeyValueError> { + (self.0)(Token::I64(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> Result<(), KeyValueError> { + (self.0)(Token::F64(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> Result<(), KeyValueError> { + (self.0)(Token::Bool(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> Result<(), KeyValueError> { + (self.0)(Token::Char(v)); + Ok(()) + } + + fn str(&mut self, v: &str) -> Result<(), KeyValueError> { + (self.0)(Token::Str(v)); + Ok(()) + } + + fn none(&mut self) -> Result<(), KeyValueError> { + (self.0)(Token::None); + Ok(()) + } + } + + // Check that a value retains the right structure + fn check(value: Value, expected: Token) { + let mut backend = TestBackend(|token: Token| assert_eq!(expected, token)); + value.inner.visit(&mut backend).unwrap(); + } + + check(42u64.to_value(), Token::U64(42)); + check(42i64.to_value(), Token::I64(42)); + check(42.01f64.to_value(), Token::F64(42.01)); + check(true.to_value(), Token::Bool(true)); + check('a'.to_value(), Token::Char('a')); + check(format_args!("a {}", "value").to_value(), Token::Str("a value")); + check("a loong string".to_value(), Token::Str("a loong string")); + check(Some(true).to_value(), Token::Bool(true)); + check(().to_value(), Token::None); + check(Option::None::.to_value(), Token::None); + } +} \ No newline at end of file diff --git a/src/key_values/value/mod.rs b/src/key_values/value/mod.rs new file mode 100644 index 000000000..9298f8989 --- /dev/null +++ b/src/key_values/value/mod.rs @@ -0,0 +1,73 @@ +//! Structured values. + +use std::fmt; + +mod any; +mod backend; +mod impls; + +pub use key_values::KeyValueError; + +use self::any::{Any, FromAnyFn}; + +/// A type that can be converted into a [`Value`](struct.Value.html). +pub trait ToValue { + /// Perform the conversion. + fn to_value(&self) -> Value; +} + +impl<'a, T> ToValue for &'a T +where + T: ToValue + ?Sized, +{ + fn to_value(&self) -> Value { + (**self).to_value() + } +} + +impl<'v> ToValue for Value<'v> { + fn to_value(&self) -> Value { + Value { + inner: self.inner, + } + } +} + +/// A value in a structured key-value pair. +pub struct Value<'v> { + inner: Any<'v>, +} + +impl<'v> Value<'v> { + // FIXME: The `from_any` API is intended to be public. + // Its implications need discussion first though. + fn from_any(v: &'v T, from: FromAnyFn) -> Self { + Value { + inner: Any::new(v, from) + } + } + + /// Get a value from a formattable type. + pub fn from_debug(value: &'v T) -> Self + where + T: fmt::Debug, + { + Self::from_any(value, |from, value| from.debug(value)) + } +} + +impl<'v> fmt::Debug for Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.visit(&mut self::backend::FmtBackend(f))?; + + Ok(()) + } +} + +impl<'v> fmt::Display for Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.visit(&mut self::backend::FmtBackend(f))?; + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 20fafdada..297bf1a40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -307,6 +307,9 @@ use std::sync::atomic::ATOMIC_USIZE_INIT; mod macros; mod serde; +#[cfg(feature = "kv_unstable")] +pub mod key_values; + // The LOGGER static holds a pointer to the global logger. It is protected by // the STATE static which determines whether LOGGER has been initialized yet. static mut LOGGER: &'static Log = &NopLogger; @@ -725,6 +728,19 @@ pub struct Record<'a> { module_path: Option<&'a str>, file: Option<&'a str>, line: Option, + #[cfg(feature = "kv_unstable")] + key_values: KeyValues<'a>, +} + +#[cfg(feature = "kv_unstable")] +#[derive(Clone)] +struct KeyValues<'a>(&'a key_values::Source); + +#[cfg(feature = "kv_unstable")] +impl<'a> fmt::Debug for KeyValues<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("KeyValues").finish() + } } impl<'a> Record<'a> { @@ -775,6 +791,47 @@ impl<'a> Record<'a> { pub fn line(&self) -> Option { self.line } + + /// The structued key-value pairs associated with the message. + #[cfg(feature = "kv_unstable")] + #[inline] + pub fn key_values(&self) -> &key_values::Source { + self.key_values.0 + } + + /// Create a new [`Builder`](struct.Builder.html) based on this record. + #[cfg(feature = "kv_unstable")] + #[inline] + pub fn to_builder(&self) -> RecordBuilder { + #[cfg(feature = "kv_unstable")] + return RecordBuilder { + record: Record { + metadata: Metadata { + level: self.metadata.level, + target: self.metadata.target, + }, + args: self.args, + module_path: self.module_path, + file: self.file, + line: self.line, + key_values: self.key_values.clone(), + } + }; + + #[cfg(not(feature = "kv_unstable"))] + return RecordBuilder { + record: Record { + metadata: Metadata { + level: self.metadata.level, + target: self.metadata.target, + }, + args: self.args, + module_path: self.module_path, + file: self.file, + line: self.line, + } + }; + } } /// Builder for [`Record`](struct.Record.html). @@ -837,15 +894,28 @@ impl<'a> RecordBuilder<'a> { /// [`Metadata::builder().build()`]: struct.MetadataBuilder.html#method.build #[inline] pub fn new() -> RecordBuilder<'a> { - RecordBuilder { + #[cfg(feature = "kv_unstable")] + return RecordBuilder { record: Record { args: format_args!(""), metadata: Metadata::builder().build(), module_path: None, file: None, line: None, + key_values: KeyValues(&Option::None::<(key_values::Key, key_values::Value)>), }, - } + }; + + #[cfg(not(feature = "kv_unstable"))] + return RecordBuilder { + record: Record { + args: format_args!(""), + metadata: Metadata::builder().build(), + module_path: None, + file: None, + line: None, + }, + }; } /// Set [`args`](struct.Record.html#method.args). @@ -897,6 +967,14 @@ impl<'a> RecordBuilder<'a> { self } + /// Set [`key_values`](struct.Record.html#method.key_values) + #[cfg(feature = "kv_unstable")] + #[inline] + pub fn key_values(&mut self, kvs: &'a key_values::Source) -> &mut RecordBuilder<'a> { + self.record.key_values = KeyValues(kvs); + self + } + /// Invoke the builder and return a `Record` #[inline] pub fn build(&self) -> Record<'a> { @@ -1478,4 +1556,42 @@ mod tests { assert_eq!(record_test.file(), Some("bar")); assert_eq!(record_test.line(), Some(30)); } + + #[test] + #[cfg(feature = "kv_unstable")] + fn test_record_key_values_builder() { + use super::Record; + use key_values::source::{self, Visitor}; + + struct TestVisitor { + seen_pairs: usize, + } + + impl<'kvs> Visitor<'kvs> for TestVisitor { + fn visit_pair( + &mut self, + _: source::Key<'kvs>, + _: source::Value<'kvs> + ) -> Result<(), source::KeyValueError> { + self.seen_pairs += 1; + Ok(()) + } + } + + let kvs: &[(&str, i32)] = &[ + ("a", 1), + ("b", 2) + ]; + let record_test = Record::builder() + .key_values(&kvs) + .build(); + + let mut visitor = TestVisitor { + seen_pairs: 0, + }; + + record_test.key_values().visit(&mut visitor).unwrap(); + + assert_eq!(2, visitor.seen_pairs); + } }