From 84fffab7f06909cc480871556c01c04091079faa Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Mon, 29 Apr 2019 14:32:30 +1000 Subject: [PATCH] Build out initial key values API (#324) --- .travis.yml | 13 +- Cargo.toml | 3 + src/kv/error.rs | 46 +++++ src/kv/key.rs | 143 ++++++++++++++ src/kv/mod.rs | 11 ++ src/kv/source.rs | 122 ++++++++++++ src/kv/value/impls.rs | 414 +++++++++++++++++++++++++++++++++++++++ src/kv/value/internal.rs | 92 +++++++++ src/kv/value/mod.rs | 90 +++++++++ src/lib.rs | 138 ++++++++++++- 10 files changed, 1067 insertions(+), 5 deletions(-) create mode 100644 src/kv/error.rs create mode 100644 src/kv/key.rs create mode 100644 src/kv/mod.rs create mode 100644 src/kv/source.rs create mode 100644 src/kv/value/impls.rs create mode 100644 src/kv/value/internal.rs create mode 100644 src/kv/value/mod.rs diff --git a/.travis.yml b/.travis.yml index edcc10b4f..a87b92b19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,13 +5,20 @@ rust: - stable - beta - nightly -install: - - '[ "$TRAVIS_RUST_VERSION" == "1.16.0" ] || rustup target add thumbv6m-none-eabi' +matrix: + include: + - rust: 1.21.0 + script: + - cargo test --verbose --features kv_unstable + - cargo test --verbose --features "kv_unstable std" + - rust: stable + script: + - rustup target add thumbv6m-none-eabi + - cargo build --verbose --target=thumbv6m-none-eabi script: - cargo build --verbose - cargo build --verbose --features serde - cargo build --verbose --features std - - '[ "$TRAVIS_RUST_VERSION" == "1.16.0" ] || cargo build --verbose --target=thumbv6m-none-eabi' - cargo test --verbose - cargo test --verbose --features serde - cargo test --verbose --features std diff --git a/Cargo.toml b/Cargo.toml index 8d8093e0a..4477b0dd8 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/kv/error.rs b/src/kv/error.rs new file mode 100644 index 000000000..8e91f04f8 --- /dev/null +++ b/src/kv/error.rs @@ -0,0 +1,46 @@ +use std::fmt; + +/// An error encountered while working with structured data. +#[derive(Clone, Debug)] +pub struct Error { + msg: &'static str, +} + +impl Error { + /// Create an error from the given message. + pub fn msg(msg: &'static str) -> Self { + Error { + msg: msg, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.msg.fmt(f) + } +} + +impl From for Error { + fn from(_: fmt::Error) -> Self { + Error::msg("formatting failed") + } +} + +impl From for fmt::Error { + fn from(_: Error) -> Self { + fmt::Error + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + use std::error; + + impl error::Error for Error { + fn description(&self) -> &str { + "key values error" + } + } +} diff --git a/src/kv/key.rs b/src/kv/key.rs new file mode 100644 index 000000000..82f1e85e6 --- /dev/null +++ b/src/kv/key.rs @@ -0,0 +1,143 @@ +//! Structured keys. + +use std::fmt; +use std::cmp; +use std::hash; +use std::borrow::Borrow; + +/// A type that can be converted into a [`Key`](struct.Key.html). +pub trait ToKey { + /// Perform the conversion. + 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 ToKey for str { + fn to_key(&self) -> Key { + Key::from_str(self) + } +} + +/// A key in a structured key-value pair. +#[derive(Clone)] +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> hash::Hash for Key<'k> { + fn hash(&self, state: &mut H) + where + H: hash::Hasher, + { + self.as_str().hash(state) + } +} + +impl<'k, 'ko> PartialEq> for Key<'k> { + fn eq(&self, other: &Key<'ko>) -> bool { + self.as_str().eq(other.as_str()) + } +} + +impl<'k> Eq for Key<'k> {} + +impl<'k, 'ko> PartialOrd> for Key<'k> { + fn partial_cmp(&self, other: &Key<'ko>) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl<'k> Ord for Key<'k> { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.as_str().cmp(other.as_str()) + } +} + +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()); + } +} diff --git a/src/kv/mod.rs b/src/kv/mod.rs new file mode 100644 index 000000000..f10441254 --- /dev/null +++ b/src/kv/mod.rs @@ -0,0 +1,11 @@ +//! Structured key-value pairs. + +mod error; +mod source; +mod key; +mod value; + +pub use self::error::Error; +pub use self::source::{Source, Visitor}; +pub use self::key::{Key, ToKey}; +pub use self::value::{Value, ToValue}; diff --git a/src/kv/source.rs b/src/kv/source.rs new file mode 100644 index 000000000..5218d184a --- /dev/null +++ b/src/kv/source.rs @@ -0,0 +1,122 @@ +//! Sources for key-value pairs. + +use kv::{Error, Key, ToKey, 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<(), Error>; +} + +impl<'a, T> Source for &'a T +where + T: Source + ?Sized, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), Error> { + (**self).visit(visitor) + } +} + +impl Source for (K, V) +where + K: ToKey, + V: ToValue, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), Error> { + 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<(), Error> { + 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<(), Error> { + 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<(), Error>; +} + +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<(), Error> { + (**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<(), Error> { + (**self).visit(visitor) + } + } + + impl Source for Vec + where + S: Source, + { + fn visit<'kvs>(&'kvs self, visitor: &mut Visitor<'kvs>) -> Result<(), Error> { + (**self).visit(visitor) + } + } + + impl<'kvs, V> Visitor<'kvs> for Box + where + V: Visitor<'kvs> + ?Sized, + { + fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { + (**self).visit_pair(key, value) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn source_is_object_safe() { + fn _check(_: &Source) {} + } + + #[test] + fn visitor_is_object_safe() { + fn _check(_: &Visitor) {} + } +} diff --git a/src/kv/value/impls.rs b/src/kv/value/impls.rs new file mode 100644 index 000000000..f4c01d72f --- /dev/null +++ b/src/kv/value/impls.rs @@ -0,0 +1,414 @@ +use std::fmt; + +use super::{Error, ToValue, Value, Visit, Visitor}; + +impl ToValue for usize { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for usize { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.u64(*self as u64) + } +} + +impl ToValue for isize { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for isize { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.i64(*self as i64) + } +} + +impl ToValue for u8 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for u8 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.u64(*self as u64) + } +} + +impl ToValue for u16 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for u16 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.u64(*self as u64) + } +} + +impl ToValue for u32 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for u32 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.u64(*self as u64) + } +} + +impl ToValue for u64 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for u64 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.u64(*self) + } +} + +impl ToValue for i8 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for i8 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.i64(*self as i64) + } +} + +impl ToValue for i16 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for i16 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.i64(*self as i64) + } +} + +impl ToValue for i32 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for i32 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.i64(*self as i64) + } +} + +impl ToValue for i64 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for i64 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.i64(*self) + } +} + +impl ToValue for f32 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for f32 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.f64(*self as f64) + } +} + +impl ToValue for f64 { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for f64 { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.f64(*self) + } +} + +impl ToValue for bool { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for bool { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.bool(*self) + } +} + +impl ToValue for char { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for char { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.char(*self) + } +} + +impl<'v> ToValue for &'v str { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl<'v> Visit for &'v str { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.str(*self) + } +} + +impl ToValue for () { + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for () { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.none() + } +} + +impl ToValue for Option +where + T: ToValue, +{ + fn to_value(&self) -> Value { + Value::from_internal(self) + } +} + +impl Visit for Option +where + T: ToValue, +{ + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + match *self { + Some(ref value) => value.to_value().visit(visitor), + None => visitor.none(), + } + } +} + +impl<'v> ToValue for fmt::Arguments<'v> { + fn to_value(&self) -> Value { + Value::from_debug(self) + } +} + +#[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_internal(self) + } + } + + impl Visit for String { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.str(&*self) + } + } + + impl<'a> ToValue for Cow<'a, str> { + fn to_value(&self) -> Value { + Value::from_internal(self) + } + } + + impl<'a> Visit for Cow<'a, str> { + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + visitor.str(&*self) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use kv::value::Error; + use kv::value::internal::Visitor; + + 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 TestVisitor(F); + + impl Visitor for TestVisitor + where + F: Fn(Token), + { + fn debug(&mut self, v: &fmt::Debug) -> Result<(), Error> { + let mut buf = Buffer::new(); + write!(&mut buf, "{:?}", v)?; + + let s = buf.as_str().map_err(|_| Error::msg("invalid UTF8"))?; + (self.0)(Token::Str(s)); + Ok(()) + } + + fn u64(&mut self, v: u64) -> Result<(), Error> { + (self.0)(Token::U64(v)); + Ok(()) + } + + fn i64(&mut self, v: i64) -> Result<(), Error> { + (self.0)(Token::I64(v)); + Ok(()) + } + + fn f64(&mut self, v: f64) -> Result<(), Error> { + (self.0)(Token::F64(v)); + Ok(()) + } + + fn bool(&mut self, v: bool) -> Result<(), Error> { + (self.0)(Token::Bool(v)); + Ok(()) + } + + fn char(&mut self, v: char) -> Result<(), Error> { + (self.0)(Token::Char(v)); + Ok(()) + } + + fn str(&mut self, v: &str) -> Result<(), Error> { + (self.0)(Token::Str(v)); + Ok(()) + } + + fn none(&mut self) -> Result<(), Error> { + (self.0)(Token::None); + Ok(()) + } + } + + // Check that a value retains the right structure + fn check(value: Value, expected: Token) { + let mut visitor = TestVisitor(|token: Token| assert_eq!(expected, token)); + value.visit(&mut visitor).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); + } +} diff --git a/src/kv/value/internal.rs b/src/kv/value/internal.rs new file mode 100644 index 000000000..d03c1a8b8 --- /dev/null +++ b/src/kv/value/internal.rs @@ -0,0 +1,92 @@ +use std::fmt; + +use super::Error; + +// `Visit` and `Visitor` is an internal API for visiting the structure of a value. +// It's not intended to be public (at this stage). +// +// 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 that original structure. + +/// A container for a structured value for a specific kind of visitor. +#[derive(Clone, Copy)] +pub(super) enum Inner<'v> { + /// An internal `Visit`. It'll be an internal structure-preserving + /// type from the standard library that's implemented in this crate. + Internal(&'v Visit), + /// A debuggable value. + Debug(&'v fmt::Debug), + /// A displayable value. + Display(&'v fmt::Display), +} + +impl<'v> Inner<'v> { + pub(super) fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + match *self { + Inner::Internal(ref value) => value.visit(visitor), + Inner::Debug(ref value) => visitor.debug(value), + Inner::Display(ref value) => visitor.display(value), + } + } +} + +/// An internal structure-preserving value. +pub(super) trait Visit { + fn visit(&self, backend: &mut Visitor) -> Result<(), Error>; +} + +/// The internal serialization contract. +pub(super) trait Visitor { + fn debug(&mut self, v: &fmt::Debug) -> Result<(), Error>; + fn display(&mut self, v: &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>; +} + +/// A visitor for `std::fmt`. +pub(super) struct FmtVisitor<'a, 'b: 'a>(pub(super) &'a mut fmt::Formatter<'b>); + +impl<'a, 'b: 'a> Visitor for FmtVisitor<'a, 'b> { + fn debug(&mut self, v: &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")) + } +} diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs new file mode 100644 index 000000000..ff0db6c63 --- /dev/null +++ b/src/kv/value/mod.rs @@ -0,0 +1,90 @@ +//! Structured values. + +use std::fmt; + +mod internal; +mod impls; + +use kv::Error; + +use self::internal::{Inner, Visit, Visitor}; + +/// 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: Inner<'v>, +} + +impl<'v> Value<'v> { + /// Get a value from an internal `Visit`. + fn from_internal(value: &'v T) -> Self + where + T: Visit, + { + Value { + inner: Inner::Internal(value), + } + } + + /// Get a value from a debuggable type. + pub fn from_debug(value: &'v T) -> Self + where + T: fmt::Debug, + { + Value { + inner: Inner::Debug(value), + } + } + + /// Get a value from a displayable type. + pub fn from_display(value: &'v T) -> Self + where + T: fmt::Display, + { + Value { + inner: Inner::Display(value), + } + } + + fn visit(&self, visitor: &mut Visitor) -> Result<(), Error> { + self.inner.visit(visitor) + } +} + +impl<'v> fmt::Debug for Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.visit(&mut self::internal::FmtVisitor(f))?; + + Ok(()) + } +} + +impl<'v> fmt::Display for Value<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.visit(&mut self::internal::FmtVisitor(f))?; + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 42e579561..855ddc835 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 kv; + // 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,37 @@ pub struct Record<'a> { module_path: Option<&'a str>, file: Option<&'a str>, line: Option, + #[cfg(feature = "kv_unstable")] + key_values: KeyValues<'a>, +} + +// This wrapper type is only needed so we can +// `#[derive(Debug)]` on `Record`. It also +// provides a useful `Debug` implementation for +// the underlying `Source`. +#[cfg(feature = "kv_unstable")] +#[derive(Clone)] +struct KeyValues<'a>(&'a kv::Source); + +#[cfg(feature = "kv_unstable")] +impl<'a> fmt::Debug for KeyValues<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::kv::{Key, Value, Visitor, Error}; + + struct FmtVisitor<'a, 'b: 'a>(fmt::DebugMap<'a, 'b>); + + impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for FmtVisitor<'a, 'b> { + fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { + self.0.entry(&key, &value); + + Ok(()) + } + } + + let mut visitor = FmtVisitor(f.debug_map()); + self.0.visit(&mut visitor)?; + visitor.0.finish() + } } impl<'a> Record<'a> { @@ -775,6 +809,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) -> &kv::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 +912,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::<(kv::Key, kv::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 +985,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 kv::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> { @@ -1523,4 +1619,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 kv::{self, Visitor}; + + struct TestVisitor { + seen_pairs: usize, + } + + impl<'kvs> Visitor<'kvs> for TestVisitor { + fn visit_pair( + &mut self, + _: kv::Key<'kvs>, + _: kv::Value<'kvs> + ) -> Result<(), kv::Error> { + 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); + } }