diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3fc335e73..3c263a434 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,10 +44,10 @@ jobs: - run: cargo test --verbose --all-features - run: cargo test --verbose --features serde - run: cargo test --verbose --features std - - run: cargo test --verbose --features kv_unstable - - run: cargo test --verbose --features kv_unstable_sval - - run: cargo test --verbose --features kv_unstable_serde - - run: cargo test --verbose --features "kv_unstable kv_unstable_std kv_unstable_sval kv_unstable_serde" + - run: cargo test --verbose --features kv + - run: cargo test --verbose --features kv_sval + - run: cargo test --verbose --features kv_serde + - run: cargo test --verbose --features "kv kv_std kv_sval kv_serde" - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml --release @@ -103,12 +103,12 @@ jobs: run: | rustup update nightly --no-self-update rustup default nightly - - run: cargo build --verbose -Z avoid-dev-deps --features kv_unstable - - run: cargo build --verbose -Z avoid-dev-deps --features "kv_unstable std" - - run: cargo build --verbose -Z avoid-dev-deps --features "kv_unstable kv_unstable_sval" - - run: cargo build --verbose -Z avoid-dev-deps --features "kv_unstable kv_unstable_serde" - - run: cargo build --verbose -Z avoid-dev-deps --features "kv_unstable kv_unstable_std" - - run: cargo build --verbose -Z avoid-dev-deps --features "kv_unstable kv_unstable_sval kv_unstable_serde" + - run: cargo build --verbose -Z avoid-dev-deps --features kv + - run: cargo build --verbose -Z avoid-dev-deps --features "kv std" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_sval" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_serde" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_std" + - run: cargo build --verbose -Z avoid-dev-deps --features "kv kv_sval kv_serde" minimalv: name: Minimal versions @@ -119,12 +119,12 @@ jobs: run: | rustup update nightly --no-self-update rustup default nightly - - run: cargo build --verbose -Z minimal-versions --features kv_unstable - - run: cargo build --verbose -Z minimal-versions --features "kv_unstable std" - - run: cargo build --verbose -Z minimal-versions --features "kv_unstable kv_unstable_sval" - - run: cargo build --verbose -Z minimal-versions --features "kv_unstable kv_unstable_serde" - - run: cargo build --verbose -Z minimal-versions --features "kv_unstable kv_unstable_std" - - run: cargo build --verbose -Z minimal-versions --features "kv_unstable kv_unstable_sval kv_unstable_serde" + - run: cargo build --verbose -Z minimal-versions --features kv + - run: cargo build --verbose -Z minimal-versions --features "kv std" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_sval" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_serde" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_std" + - run: cargo build --verbose -Z minimal-versions --features "kv kv_sval kv_serde" msrv: name: MSRV @@ -135,7 +135,9 @@ jobs: run: | rustup update 1.60.0 --no-self-update rustup default 1.60.0 - - run: cargo test --verbose --manifest-path tests/Cargo.toml + - run: | + cargo test --verbose --manifest-path tests/Cargo.toml + cargo test --verbose --manifest-path tests/Cargo.toml --features kv embedded: name: Embedded diff --git a/Cargo.toml b/Cargo.toml index 89012be02..cf64b43e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.60.0" edition = "2021" [package.metadata.docs.rs] -features = ["std", "serde", "kv_unstable_std", "kv_unstable_sval", "kv_unstable_serde"] +features = ["std", "serde", "kv_std", "kv_sval", "kv_serde"] [[test]] name = "filters" @@ -46,25 +46,31 @@ release_max_level_trace = [] std = [] -# requires the latest stable -# this will have a tighter MSRV before stabilization -kv_unstable = ["value-bag"] -kv_unstable_sval = ["kv_unstable", "value-bag/sval", "sval", "sval_ref"] -kv_unstable_std = ["std", "kv_unstable", "value-bag/error"] -kv_unstable_serde = ["kv_unstable_std", "value-bag/serde", "serde"] +kv = [] +kv_sval = ["kv", "value-bag/sval", "sval", "sval_ref"] +kv_std = ["std", "kv", "value-bag/error"] +kv_serde = ["kv_std", "value-bag/serde", "serde"] + +# Deprecated: use `kv_*` instead +# These `*_unstable` features will be removed in a future release +kv_unstable = ["kv", "value-bag"] +kv_unstable_sval = ["kv_sval", "kv_unstable"] +kv_unstable_std = ["kv_std", "kv_unstable"] +kv_unstable_serde = ["kv_serde", "kv_unstable_std"] [dependencies] serde = { version = "1.0", optional = true, default-features = false } sval = { version = "2.1", optional = true, default-features = false } sval_ref = { version = "2.1", optional = true, default-features = false } -value-bag = { version = "1.4", optional = true, default-features = false } +value-bag = { version = "1.7", optional = true, default-features = false, features = ["inline-i128"] } [dev-dependencies] serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" serde_test = "1.0" sval = { version = "2.1" } sval_derive = { version = "2.1" } -value-bag = { version = "1.4", features = ["test"] } +value-bag = { version = "1.7", features = ["test"] } # NOTE: log doesn't actually depent on this crate. However our dependencies, # serde and sval, dependent on version 1.0 of the crate, which has problem fixed diff --git a/README.md b/README.md index d41414473..d6a08f558 100644 --- a/README.md +++ b/README.md @@ -100,23 +100,28 @@ The executable itself may use the `log` crate to log as well. ## Structured logging -If you enable the `kv_unstable` feature, you can associate structured data with your log records: +If you enable the `kv` feature, you can associate structured data with your log records: ```rust -use log::{info, trace, warn, as_serde, as_error}; +use log::{info, trace, warn}; pub fn shave_the_yak(yak: &mut Yak) { - trace!(target = "yak_events", yak = as_serde!(yak); "Commencing yak shaving"); + // `yak:serde` will capture `yak` using its `serde::Serialize` impl + // + // You could also use `:?` for `Debug`, or `:%` for `Display`. For a + // full list, see the `log` crate documentation + trace!(target = "yak_events", yak:serde; "Commencing yak shaving"); loop { match find_a_razor() { Ok(razor) => { - info!(razor = razor; "Razor located"); + info!(razor; "Razor located"); yak.shave(razor); break; } - Err(err) => { - warn!(err = as_error!(err); "Unable to locate a razor, retrying"); + Err(e) => { + // `e:err` will capture `e` using its `std::error::Error` impl + warn!(e:err; "Unable to locate a razor, retrying"); } } } diff --git a/benches/value.rs b/benches/value.rs index e43be50f7..3d0f18bfe 100644 --- a/benches/value.rs +++ b/benches/value.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "kv_unstable")] +#![cfg(feature = "kv")] #![feature(test)] use log::kv::Value; diff --git a/src/__private_api.rs b/src/__private_api.rs index 92bd15656..fd0a5a762 100644 --- a/src/__private_api.rs +++ b/src/__private_api.rs @@ -5,31 +5,28 @@ use crate::{Level, Metadata, Record}; use std::fmt::Arguments; pub use std::{file, format_args, line, module_path, stringify}; -#[cfg(feature = "kv_unstable")] -pub type Value<'a> = dyn crate::kv::value::ToValue + 'a; - -#[cfg(not(feature = "kv_unstable"))] -pub type Value<'a> = str; +#[cfg(not(feature = "kv"))] +pub type Value<'a> = &'a str; mod sealed { /// Types for the `kv` argument. pub trait KVs<'a> { - fn into_kvs(self) -> Option<&'a [(&'a str, &'a super::Value<'a>)]>; + fn into_kvs(self) -> Option<&'a [(&'a str, super::Value<'a>)]>; } } // Types for the `kv` argument. -impl<'a> KVs<'a> for &'a [(&'a str, &'a Value<'a>)] { +impl<'a> KVs<'a> for &'a [(&'a str, Value<'a>)] { #[inline] - fn into_kvs(self) -> Option<&'a [(&'a str, &'a Value<'a>)]> { + fn into_kvs(self) -> Option<&'a [(&'a str, Value<'a>)]> { Some(self) } } impl<'a> KVs<'a> for () { #[inline] - fn into_kvs(self) -> Option<&'a [(&'a str, &'a Value<'a>)]> { + fn into_kvs(self) -> Option<&'a [(&'a str, Value<'a>)]> { None } } @@ -41,13 +38,11 @@ fn log_impl( level: Level, &(target, module_path, file): &(&str, &'static str, &'static str), line: u32, - kvs: Option<&[(&str, &Value)]>, + kvs: Option<&[(&str, Value)]>, ) { - #[cfg(not(feature = "kv_unstable"))] + #[cfg(not(feature = "kv"))] if kvs.is_some() { - panic!( - "key-value support is experimental and must be enabled using the `kv_unstable` feature" - ) + panic!("key-value support is experimental and must be enabled using the `kv` feature") } let mut builder = Record::builder(); @@ -60,7 +55,7 @@ fn log_impl( .file_static(Some(file)) .line(Some(line)); - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] builder.key_values(&kvs); crate::logger().log(&builder.build()); @@ -87,3 +82,44 @@ pub fn log<'a, K>( pub fn enabled(level: Level, target: &str) -> bool { crate::logger().enabled(&Metadata::builder().level(level).target(target).build()) } + +#[cfg(feature = "kv")] +mod kv_support { + use crate::kv; + + pub type Value<'a> = kv::Value<'a>; + + // NOTE: Many functions here accept a double reference &&V + // This is so V itself can be ?Sized, while still letting us + // erase it to some dyn Trait (because &T is sized) + + pub fn capture_to_value<'a, V: kv::ToValue + ?Sized>(v: &'a &'a V) -> Value<'a> { + v.to_value() + } + + pub fn capture_debug<'a, V: core::fmt::Debug + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_debug(v) + } + + pub fn capture_display<'a, V: core::fmt::Display + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_display(v) + } + + #[cfg(feature = "kv_std")] + pub fn capture_error<'a>(v: &'a (dyn std::error::Error + 'static)) -> Value<'a> { + Value::from_dyn_error(v) + } + + #[cfg(feature = "kv_sval")] + pub fn capture_sval<'a, V: sval::Value + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_sval(v) + } + + #[cfg(feature = "kv_serde")] + pub fn capture_serde<'a, V: serde::Serialize + ?Sized>(v: &'a &'a V) -> Value<'a> { + Value::from_serde(v) + } +} + +#[cfg(feature = "kv")] +pub use self::kv_support::*; diff --git a/src/kv/error.rs b/src/kv/error.rs index 9643a47f2..7efa5af36 100644 --- a/src/kv/error.rs +++ b/src/kv/error.rs @@ -11,7 +11,8 @@ enum Inner { #[cfg(feature = "std")] Boxed(std_support::BoxedError), Msg(&'static str), - Value(value_bag::Error), + #[cfg(feature = "value-bag")] + Value(crate::kv::value::inner::Error), Fmt, } @@ -23,21 +24,23 @@ impl Error { } } - // Not public so we don't leak the `value_bag` API - pub(super) fn from_value(err: value_bag::Error) -> Self { + // Not public so we don't leak the `crate::kv::value::inner` API + #[cfg(feature = "value-bag")] + pub(super) fn from_value(err: crate::kv::value::inner::Error) -> Self { Error { inner: Inner::Value(err), } } - // Not public so we don't leak the `value_bag` API - pub(super) fn into_value(self) -> value_bag::Error { + // Not public so we don't leak the `crate::kv::value::inner` API + #[cfg(feature = "value-bag")] + pub(super) fn into_value(self) -> crate::kv::value::inner::Error { match self.inner { Inner::Value(err) => err, - #[cfg(feature = "kv_unstable_std")] - _ => value_bag::Error::boxed(self), - #[cfg(not(feature = "kv_unstable_std"))] - _ => value_bag::Error::msg("error inspecting a value"), + #[cfg(feature = "kv_std")] + _ => crate::kv::value::inner::Error::boxed(self), + #[cfg(not(feature = "kv_std"))] + _ => crate::kv::value::inner::Error::msg("error inspecting a value"), } } } @@ -48,6 +51,7 @@ impl fmt::Display for Error { match &self.inner { #[cfg(feature = "std")] Boxed(err) => err.fmt(f), + #[cfg(feature = "value-bag")] Value(err) => err.fmt(f), Msg(msg) => msg.fmt(f), Fmt => fmt::Error.fmt(f), diff --git a/src/kv/key.rs b/src/kv/key.rs index 858ec493a..9a64b956f 100644 --- a/src/kv/key.rs +++ b/src/kv/key.rs @@ -30,7 +30,7 @@ impl ToKey for str { } } -/// A key in a structured key-value pair. +/// A key in a key-value. // These impls must only be based on the as_str() representation of the key // If a new field (such as an optional index) is added to the key they must not affect comparison #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -93,7 +93,7 @@ mod std_support { } } -#[cfg(feature = "kv_unstable_sval")] +#[cfg(feature = "kv_sval")] mod sval_support { use super::*; @@ -116,7 +116,7 @@ mod sval_support { } } -#[cfg(feature = "kv_unstable_serde")] +#[cfg(feature = "kv_serde")] mod serde_support { use super::*; diff --git a/src/kv/mod.rs b/src/kv/mod.rs index 5dc69337c..1ccb82514 100644 --- a/src/kv/mod.rs +++ b/src/kv/mod.rs @@ -1,26 +1,265 @@ -//! **UNSTABLE:** Structured key-value pairs. +//! Structured logging. //! -//! This module is unstable and breaking changes may be made -//! at any time. See [the tracking issue](https://github.com/rust-lang-nursery/log/issues/328) -//! for more details. -//! -//! Add the `kv_unstable` feature to your `Cargo.toml` to enable +//! Add the `kv` feature to your `Cargo.toml` to enable //! this module: //! //! ```toml //! [dependencies.log] -//! features = ["kv_unstable"] +//! features = ["kv"] +//! ``` +//! +//! # Structured logging in `log` +//! +//! Structured logging enhances traditional text-based log records with user-defined +//! attributes. Structured logs can be analyzed using a variety of data processing +//! techniques, without needing to find and parse attributes from unstructured text first. +//! +//! In `log`, user-defined attributes are part of a [`Source`] on the log record. +//! Each attribute is a key-value; a pair of [`Key`] and [`Value`]. Keys are strings +//! and values are a datum of any type that can be formatted or serialized. Simple types +//! like strings, booleans, and numbers are supported, as well as arbitrarily complex +//! structures involving nested objects and sequences. +//! +//! ## Adding key-values to log records +//! +//! Key-values appear before the message format in the `log!` macros: +//! +//! ``` +//! # use log::info; +//! info!(a = 1; "Something of interest"); +//! ``` +//! +//! Key-values support the same shorthand identifer syntax as `format_args`: +//! +//! ``` +//! # use log::info; +//! let a = 1; +//! +//! info!(a; "Something of interest"); +//! ``` +//! +//! Values are capturing using the [`ToValue`] trait by default. To capture a value +//! using a different trait implementation, use a modifier after its key. Here's how +//! the same example can capture `a` using its `Debug` implementation instead: +//! +//! ``` +//! # use log::info; +//! info!(a:? = 1; "Something of interest"); +//! ``` +//! +//! The following capturing modifiers are supported: +//! +//! - `:?` will capture the value using `Debug`. +//! - `:debug` will capture the value using `Debug`. +//! - `:%` will capture the value using `Display`. +//! - `:display` will capture the value using `Display`. +//! - `:err` will capture the value using `std::error::Error` (requires the `kv_std` feature). +//! - `:sval` will capture the value using `sval::Value` (requires the `kv_sval` feature). +//! - `:serde` will capture the value using `serde::Serialize` (requires the `kv_serde` feature). +//! +//! ## Working with key-values on log records +//! +//! Use the [`Record::key_values`](../struct.Record.html#method.key_values) method to access key-values. +//! +//! Individual values can be pulled from the source by their key: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use log::kv::{Source, Key, Value}; +//! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); +//! +//! // info!(a = 1; "Something of interest"); +//! +//! let a: Value = record.key_values().get(Key::from("a")).unwrap(); +//! assert_eq!(1, a.to_i64().unwrap()); +//! # Ok(()) +//! # } +//! ``` +//! +//! All key-values can also be enumerated using a [`VisitSource`]: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use std::collections::BTreeMap; +//! +//! use log::kv::{self, Source, Key, Value, VisitSource}; +//! +//! struct Collect<'kvs>(BTreeMap, Value<'kvs>>); +//! +//! impl<'kvs> VisitSource<'kvs> for Collect<'kvs> { +//! fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { +//! self.0.insert(key, value); +//! +//! Ok(()) +//! } +//! } +//! +//! let mut visitor = Collect(BTreeMap::new()); +//! +//! # let record = log::Record::builder().key_values(&[("a", 1), ("b", 2), ("c", 3)]).build(); +//! // info!(a = 1, b = 2, c = 3; "Something of interest"); +//! +//! record.key_values().visit(&mut visitor)?; +//! +//! let collected = visitor.0; +//! +//! assert_eq!( +//! vec!["a", "b", "c"], +//! collected +//! .keys() +//! .map(|k| k.as_str()) +//! .collect::>(), +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! [`Value`]s have methods for conversions to common types: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use log::kv::{Source, Key}; +//! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); +//! +//! // info!(a = 1; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!(1, a.to_i64().unwrap()); +//! # Ok(()) +//! # } +//! ``` +//! +//! Values also have their own [`VisitValue`] type. Value visitors are a lightweight +//! API for working with primitives types: +//! +//! ``` +//! # fn main() -> Result<(), log::kv::Error> { +//! use log::kv::{self, Source, Key, VisitValue}; +//! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); +//! +//! struct IsNumeric(bool); +//! +//! impl<'kvs> VisitValue<'kvs> for IsNumeric { +//! fn visit_any(&mut self, _value: kv::Value) -> Result<(), kv::Error> { +//! self.0 = false; +//! Ok(()) +//! } +//! +//! fn visit_u64(&mut self, _value: u64) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_i64(&mut self, _value: i64) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_u128(&mut self, _value: u128) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_i128(&mut self, _value: i128) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! +//! fn visit_f64(&mut self, _value: f64) -> Result<(), kv::Error> { +//! self.0 = true; +//! Ok(()) +//! } +//! } +//! +//! // info!(a = 1; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! let mut visitor = IsNumeric(false); +//! +//! a.visit(&mut visitor)?; +//! +//! let is_numeric = visitor.0; +//! +//! assert!(is_numeric); +//! # Ok(()) +//! # } +//! ``` +//! +//! To serialize a value to a format like JSON, you can also use either `serde` or `sval`: +//! +//! ``` +//! # fn main() -> Result<(), Box> { +//! # #[cfg(feature = "serde")] +//! # { +//! # use log::kv::Key; +//! #[derive(serde::Serialize)] +//! struct Data { +//! a: i32, b: bool, +//! c: &'static str, +//! } +//! +//! let data = Data { a: 1, b: true, c: "Some data" }; +//! +//! # let source = [("a", log::kv::Value::from_serde(&data))]; +//! # let record = log::Record::builder().key_values(&source).build(); +//! // info!(a = data; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!("{\"a\":1,\"b\":true,\"c\":\"Some data\"}", serde_json::to_string(&a)?); +//! # } +//! # Ok(()) +//! # } +//! ``` +//! +//! The choice of serialization framework depends on the needs of the consumer. +//! If you're in a no-std environment, you can use `sval`. In other cases, you can use `serde`. +//! Log producers and log consumers don't need to agree on the serialization framework. +//! A value can be captured using its `serde::Serialize` implementation and still be serialized +//! through `sval` without losing any structure or data. +//! +//! Values can also always be formatted using the standard `Debug` and `Display` +//! traits: +//! +//! ``` +//! # use log::kv::Key; +//! # #[derive(Debug)] +//! struct Data { +//! a: i32, +//! b: bool, +//! c: &'static str, +//! } +//! +//! let data = Data { a: 1, b: true, c: "Some data" }; +//! +//! # let source = [("a", log::kv::Value::from_debug(&data))]; +//! # let record = log::Record::builder().key_values(&source).build(); +//! // info!(a = data; "Something of interest"); +//! +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!("Data { a: 1, b: true, c: \"Some data\" }", format!("{a:?}")); //! ``` mod error; mod key; -pub mod source; -pub mod value; +#[cfg(not(feature = "kv_unstable"))] +mod source; +#[cfg(not(feature = "kv_unstable"))] +mod value; pub use self::error::Error; pub use self::key::{Key, ToKey}; -pub use self::source::{Source, Visitor}; +pub use self::source::{Source, VisitSource}; +pub use self::value::{ToValue, Value, VisitValue}; + +#[cfg(feature = "kv_unstable")] +pub mod source; +#[cfg(feature = "kv_unstable")] +pub mod value; -#[doc(inline)] -pub use self::value::{ToValue, Value}; +#[cfg(feature = "kv_unstable")] +pub use self::source::Visitor; diff --git a/src/kv/source.rs b/src/kv/source.rs index 9c56f8b76..0ca267ce3 100644 --- a/src/kv/source.rs +++ b/src/kv/source.rs @@ -1,25 +1,65 @@ -//! Sources for key-value pairs. +//! Sources for key-values. +//! +//! This module defines the [`Source`] type and supporting APIs for +//! working with collections of key-values. use crate::kv::{Error, Key, ToKey, ToValue, Value}; use std::fmt; -/// A source of key-value pairs. +/// A source of key-values. /// /// The source may be a single pair, a set of pairs, or a filter over a set of pairs. -/// Use the [`Visitor`](trait.Visitor.html) trait to inspect the structured data +/// Use the [`VisitSource`](trait.VisitSource.html) trait to inspect the structured data /// in a source. +/// +/// A source is like an iterator over its key-values, except with a push-based API +/// instead of a pull-based one. +/// +/// # Examples +/// +/// Enumerating the key-values in a source: +/// +/// ``` +/// # fn main() -> Result<(), log::kv::Error> { +/// use log::kv::{self, Source, Key, Value, VisitSource}; +/// +/// // A `VisitSource` that prints all key-values +/// // VisitSources are fed the key-value pairs of each key-values +/// struct Printer; +/// +/// impl<'kvs> VisitSource<'kvs> for Printer { +/// fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { +/// println!("{key}: {value}"); +/// +/// Ok(()) +/// } +/// } +/// +/// // A source with 3 key-values +/// // Common collection types implement the `Source` trait +/// let source = &[ +/// ("a", 1), +/// ("b", 2), +/// ("c", 3), +/// ]; +/// +/// // Pass an instance of the `VisitSource` to a `Source` to visit it +/// source.visit(&mut Printer)?; +/// # Ok(()) +/// # } +/// ``` pub trait Source { - /// Visit key-value pairs. + /// Visit key-values. /// - /// A source doesn't have to guarantee any ordering or uniqueness of key-value pairs. + /// A source doesn't have to guarantee any ordering or uniqueness of key-values. /// If the given visitor returns an error then the source may early-return with it, - /// even if there are more key-value pairs. + /// even if there are more key-values. /// /// # Implementation notes /// - /// A source should yield the same key-value pairs to a subsequent visitor unless + /// A source should yield the same key-values to a subsequent visitor unless /// that visitor itself fails. - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error>; + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error>; /// Get the value for a given key. /// @@ -34,15 +74,14 @@ pub trait Source { get_default(self, key) } - /// Count the number of key-value pairs that can be visited. + /// Count the number of key-values that can be visited. /// /// # Implementation notes /// - /// A source that knows the number of key-value pairs upfront may provide a more + /// A source that knows the number of key-values upfront may provide a more /// efficient implementation. /// - /// A subsequent call to `visit` should yield the same number of key-value pairs - /// to the visitor, unless that visitor fails part way through. + /// A subsequent call to `visit` should yield the same number of key-values. fn count(&self) -> usize { count_default(self) } @@ -55,7 +94,7 @@ fn get_default<'v>(source: &'v (impl Source + ?Sized), key: Key) -> Option>, } - impl<'k, 'kvs> Visitor<'kvs> for Get<'k, 'kvs> { + impl<'k, 'kvs> VisitSource<'kvs> for Get<'k, 'kvs> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { if self.key == key { self.found = Some(value); @@ -75,7 +114,7 @@ fn get_default<'v>(source: &'v (impl Source + ?Sized), key: Key) -> Option usize { struct Count(usize); - impl<'kvs> Visitor<'kvs> for Count { + impl<'kvs> VisitSource<'kvs> for Count { fn visit_pair(&mut self, _: Key<'kvs>, _: Value<'kvs>) -> Result<(), Error> { self.0 += 1; @@ -92,7 +131,7 @@ impl<'a, T> Source for &'a T where T: Source + ?Sized, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } @@ -110,7 +149,7 @@ where K: ToKey, V: ToValue, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { visitor.visit_pair(self.0.to_key(), self.1.to_value()) } @@ -131,7 +170,7 @@ impl Source for [S] where S: Source, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { for source in self { source.visit(visitor)?; } @@ -154,11 +193,28 @@ where } } +impl Source for [S; N] +where + S: Source, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { + Source::visit(self as &[_], visitor) + } + + fn get(&self, key: Key) -> Option> { + Source::get(self as &[_], key) + } + + fn count(&self) -> usize { + Source::count(self as &[_]) + } +} + impl Source for Option where S: Source, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { if let Some(source) = self { source.visit(visitor)?; } @@ -176,42 +232,42 @@ where } /// A visitor for the key-value pairs in a [`Source`](trait.Source.html). -pub trait Visitor<'kvs> { +pub trait VisitSource<'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 +impl<'a, 'kvs, T> VisitSource<'kvs> for &'a mut T where - T: Visitor<'kvs> + ?Sized, + T: VisitSource<'kvs> + ?Sized, { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { (**self).visit_pair(key, value) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugMap<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugMap<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.entry(&key, &value); Ok(()) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugList<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugList<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.entry(&(key, value)); Ok(()) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugSet<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugSet<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.entry(&(key, value)); Ok(()) } } -impl<'a, 'b: 'a, 'kvs> Visitor<'kvs> for fmt::DebugTuple<'a, 'b> { +impl<'a, 'b: 'a, 'kvs> VisitSource<'kvs> for fmt::DebugTuple<'a, 'b> { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { self.field(&key); self.field(&value); @@ -232,7 +288,7 @@ mod std_support { where S: Source + ?Sized, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } @@ -249,7 +305,7 @@ mod std_support { where S: Source + ?Sized, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } @@ -266,7 +322,7 @@ mod std_support { where S: Source + ?Sized, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } @@ -283,7 +339,7 @@ mod std_support { where S: Source, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { Source::visit(&**self, visitor) } @@ -296,9 +352,9 @@ mod std_support { } } - impl<'kvs, V> Visitor<'kvs> for Box + impl<'kvs, V> VisitSource<'kvs> for Box where - V: Visitor<'kvs> + ?Sized, + V: VisitSource<'kvs> + ?Sized, { fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> { (**self).visit_pair(key, value) @@ -311,7 +367,7 @@ mod std_support { V: ToValue, S: BuildHasher, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { for (key, value) in self { visitor.visit_pair(key.to_key(), value.to_value())?; } @@ -332,7 +388,7 @@ mod std_support { K: ToKey + Borrow + Ord, V: ToValue, { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { for (key, value) in self { visitor.visit_pair(key.to_key(), value.to_value())?; } @@ -352,7 +408,7 @@ mod std_support { mod tests { use std::collections::{BTreeMap, HashMap}; - use crate::kv::value::tests::Token; + use crate::kv::value; use super::*; @@ -366,7 +422,7 @@ mod std_support { fn get() { let source = vec![("a", 1), ("b", 2), ("a", 1)]; assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(&source, Key::from_str("a")).unwrap().to_token() ); @@ -382,7 +438,7 @@ mod std_support { assert_eq!(2, Source::count(&map)); assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(&map, Key::from_str("a")).unwrap().to_token() ); } @@ -395,16 +451,20 @@ mod std_support { assert_eq!(2, Source::count(&map)); assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(&map, Key::from_str("a")).unwrap().to_token() ); } } } +// NOTE: Deprecated; but aliases can't carry this attribute +#[cfg(feature = "kv_unstable")] +pub use VisitSource as Visitor; + #[cfg(test)] mod tests { - use crate::kv::value::tests::Token; + use crate::kv::value; use super::*; @@ -415,7 +475,7 @@ mod tests { #[test] fn visitor_is_object_safe() { - fn _check(_: &dyn Visitor) {} + fn _check(_: &dyn VisitSource) {} } #[test] @@ -426,7 +486,7 @@ mod tests { } impl Source for OnePair { - fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> { + fn visit<'kvs>(&'kvs self, visitor: &mut dyn VisitSource<'kvs>) -> Result<(), Error> { visitor.visit_pair(self.key.to_key(), self.value.to_value()) } } @@ -441,11 +501,11 @@ mod tests { fn get() { let source = &[("a", 1), ("b", 2), ("a", 1)] as &[_]; assert_eq!( - Token::I64(1), + value::inner::Token::I64(1), Source::get(source, Key::from_str("a")).unwrap().to_token() ); assert_eq!( - Token::I64(2), + value::inner::Token::I64(2), Source::get(source, Key::from_str("b")).unwrap().to_token() ); assert!(Source::get(&source, Key::from_str("c")).is_none()); diff --git a/src/kv/value.rs b/src/kv/value.rs index 1c39bef0a..1511dd02e 100644 --- a/src/kv/value.rs +++ b/src/kv/value.rs @@ -1,11 +1,12 @@ //! Structured values. +//! +//! This module defines the [`Value`] type and supporting APIs for +//! capturing and serializing them. use std::fmt; pub use crate::kv::Error; -use value_bag::ValueBag; - /// A type that can be converted into a [`Value`](struct.Value.html). pub trait ToValue { /// Perform the conversion. @@ -29,77 +30,21 @@ impl<'v> ToValue for Value<'v> { } } -/// Get a value from a type implementing `std::fmt::Debug`. -#[macro_export] -macro_rules! as_debug { - ($capture:expr) => { - $crate::kv::Value::from_debug(&$capture) - }; -} - -/// Get a value from a type implementing `std::fmt::Display`. -#[macro_export] -macro_rules! as_display { - ($capture:expr) => { - $crate::kv::Value::from_display(&$capture) - }; -} - -/// Get a value from an error. -#[cfg(feature = "kv_unstable_std")] -#[macro_export] -macro_rules! as_error { - ($capture:expr) => { - $crate::kv::Value::from_dyn_error(&$capture) - }; -} - -#[cfg(feature = "kv_unstable_serde")] -/// Get a value from a type implementing `serde::Serialize`. -#[macro_export] -macro_rules! as_serde { - ($capture:expr) => { - $crate::kv::Value::from_serde(&$capture) - }; -} - -/// Get a value from a type implementing `sval::Value`. -#[cfg(feature = "kv_unstable_sval")] -#[macro_export] -macro_rules! as_sval { - ($capture:expr) => { - $crate::kv::Value::from_sval(&$capture) - }; -} - -/// A value in a structured key-value pair. +/// A value in a key-value. +/// +/// Values are an anonymous bag containing some structured datum. /// /// # Capturing values /// /// There are a few ways to capture a value: /// -/// - Using the `Value::capture_*` methods. /// - Using the `Value::from_*` methods. /// - Using the `ToValue` trait. /// - Using the standard `From` trait. /// -/// ## Using the `Value::capture_*` methods -/// -/// `Value` offers a few constructor methods that capture values of different kinds. -/// These methods require a `T: 'static` to support downcasting. -/// -/// ``` -/// use log::kv::Value; -/// -/// let value = Value::capture_debug(&42i32); -/// -/// assert_eq!(Some(42), value.to_i64()); -/// ``` -/// /// ## Using the `Value::from_*` methods /// /// `Value` offers a few constructor methods that capture values of different kinds. -/// These methods don't require `T: 'static`, but can't support downcasting. /// /// ``` /// use log::kv::Value; @@ -133,19 +78,45 @@ macro_rules! as_sval { /// assert_eq!(Some(42), value.to_i64()); /// ``` /// +/// # Data model +/// +/// Values can hold one of a number of types: +/// +/// - **Null:** The absence of any other meaningful value. Note that +/// `Some(Value::null())` is not the same as `None`. The former is +/// `null` while the latter is `undefined`. This is important to be +/// able to tell the difference between a key-value that was logged, +/// but its value was empty (`Some(Value::null())`) and a key-value +/// that was never logged at all (`None`). +/// - **Strings:** `str`, `char`. +/// - **Booleans:** `bool`. +/// - **Integers:** `u8`-`u128`, `i8`-`i128`, `NonZero*`. +/// - **Floating point numbers:** `f32`-`f64`. +/// - **Errors:** `dyn (Error + 'static)`. +/// - **`serde`:** Any type in `serde`'s data model. +/// - **`sval`:** Any type in `sval`'s data model. +/// /// # Serialization /// -/// `Value` provides a number of ways to be serialized. +/// Values provide a number of ways to be serialized. /// /// For basic types the [`Value::visit`] method can be used to extract the /// underlying typed value. However this is limited in the amount of types -/// supported (see the [`Visit`] trait methods). +/// supported (see the [`VisitValue`] trait methods). /// /// For more complex types one of the following traits can be used: -/// * [`sval::Value`], requires the `kv_unstable_sval` feature. -/// * [`serde::Serialize`], requires the `kv_unstable_serde` feature. +/// * `sval::Value`, requires the `kv_sval` feature. +/// * `serde::Serialize`, requires the `kv_serde` feature. +/// +/// You don't need a visitor to serialize values through `serde` or `sval`. +/// +/// A value can always be serialized using any supported framework, regardless +/// of how it was captured. If, for example, a value was captured using its +/// `Display` implementation, it will serialize through `serde` as a string. If it was +/// captured as a struct using `serde`, it will also serialize as a struct +/// through `sval`, or can be formatted using a `Debug`-compatible representation. pub struct Value<'v> { - inner: ValueBag<'v>, + inner: inner::Inner<'v>, } impl<'v> Value<'v> { @@ -157,66 +128,13 @@ impl<'v> Value<'v> { value.to_value() } - /// Get a value from a type implementing `std::fmt::Debug`. - pub fn capture_debug(value: &'v T) -> Self - where - T: fmt::Debug + 'static, - { - Value { - inner: ValueBag::capture_debug(value), - } - } - - /// Get a value from a type implementing `std::fmt::Display`. - pub fn capture_display(value: &'v T) -> Self - where - T: fmt::Display + 'static, - { - Value { - inner: ValueBag::capture_display(value), - } - } - - /// Get a value from an error. - #[cfg(feature = "kv_unstable_std")] - pub fn capture_error(err: &'v T) -> Self - where - T: std::error::Error + 'static, - { - Value { - inner: ValueBag::capture_error(err), - } - } - - #[cfg(feature = "kv_unstable_serde")] - /// Get a value from a type implementing `serde::Serialize`. - pub fn capture_serde(value: &'v T) -> Self - where - T: serde::Serialize + 'static, - { - Value { - inner: ValueBag::capture_serde1(value), - } - } - - /// Get a value from a type implementing `sval::Value`. - #[cfg(feature = "kv_unstable_sval")] - pub fn capture_sval(value: &'v T) -> Self - where - T: sval::Value + 'static, - { - Value { - inner: ValueBag::capture_sval2(value), - } - } - /// Get a value from a type implementing `std::fmt::Debug`. pub fn from_debug(value: &'v T) -> Self where T: fmt::Debug, { Value { - inner: ValueBag::from_debug(value), + inner: inner::Inner::from_debug(value), } } @@ -226,144 +144,77 @@ impl<'v> Value<'v> { T: fmt::Display, { Value { - inner: ValueBag::from_display(value), + inner: inner::Inner::from_display(value), } } /// Get a value from a type implementing `serde::Serialize`. - #[cfg(feature = "kv_unstable_serde")] + #[cfg(feature = "kv_serde")] pub fn from_serde(value: &'v T) -> Self where T: serde::Serialize, { Value { - inner: ValueBag::from_serde1(value), + inner: inner::Inner::from_serde1(value), } } /// Get a value from a type implementing `sval::Value`. - #[cfg(feature = "kv_unstable_sval")] + #[cfg(feature = "kv_sval")] pub fn from_sval(value: &'v T) -> Self where T: sval::Value, { Value { - inner: ValueBag::from_sval2(value), + inner: inner::Inner::from_sval2(value), } } /// Get a value from a dynamic `std::fmt::Debug`. pub fn from_dyn_debug(value: &'v dyn fmt::Debug) -> Self { Value { - inner: ValueBag::from_dyn_debug(value), + inner: inner::Inner::from_dyn_debug(value), } } /// Get a value from a dynamic `std::fmt::Display`. pub fn from_dyn_display(value: &'v dyn fmt::Display) -> Self { Value { - inner: ValueBag::from_dyn_display(value), + inner: inner::Inner::from_dyn_display(value), } } /// Get a value from a dynamic error. - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] pub fn from_dyn_error(err: &'v (dyn std::error::Error + 'static)) -> Self { Value { - inner: ValueBag::from_dyn_error(err), + inner: inner::Inner::from_dyn_error(err), + } + } + + /// Get a `null` value. + pub fn null() -> Self { + Value { + inner: inner::Inner::empty(), } } /// Get a value from an internal primitive. - fn from_value_bag(value: T) -> Self + fn from_inner(value: T) -> Self where - T: Into>, + T: Into>, { Value { inner: value.into(), } } - /// Check whether this value can be downcast to `T`. - pub fn is(&self) -> bool { - self.inner.is::() - } - - /// Try downcast this value to `T`. - pub fn downcast_ref(&self) -> Option<&T> { - self.inner.downcast_ref::() - } - /// Inspect this value using a simple visitor. - pub fn visit(&self, visitor: impl Visit<'v>) -> Result<(), Error> { - struct Visitor(V); - - impl<'v, V> value_bag::visit::Visit<'v> for Visitor - where - V: Visit<'v>, - { - fn visit_any(&mut self, value: ValueBag) -> Result<(), value_bag::Error> { - self.0 - .visit_any(Value { inner: value }) - .map_err(Error::into_value) - } - - fn visit_u64(&mut self, value: u64) -> Result<(), value_bag::Error> { - self.0.visit_u64(value).map_err(Error::into_value) - } - - fn visit_i64(&mut self, value: i64) -> Result<(), value_bag::Error> { - self.0.visit_i64(value).map_err(Error::into_value) - } - - fn visit_u128(&mut self, value: u128) -> Result<(), value_bag::Error> { - self.0.visit_u128(value).map_err(Error::into_value) - } - - fn visit_i128(&mut self, value: i128) -> Result<(), value_bag::Error> { - self.0.visit_i128(value).map_err(Error::into_value) - } - - fn visit_f64(&mut self, value: f64) -> Result<(), value_bag::Error> { - self.0.visit_f64(value).map_err(Error::into_value) - } - - fn visit_bool(&mut self, value: bool) -> Result<(), value_bag::Error> { - self.0.visit_bool(value).map_err(Error::into_value) - } - - fn visit_str(&mut self, value: &str) -> Result<(), value_bag::Error> { - self.0.visit_str(value).map_err(Error::into_value) - } - - fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), value_bag::Error> { - self.0.visit_borrowed_str(value).map_err(Error::into_value) - } - - fn visit_char(&mut self, value: char) -> Result<(), value_bag::Error> { - self.0.visit_char(value).map_err(Error::into_value) - } - - #[cfg(feature = "kv_unstable_std")] - fn visit_error( - &mut self, - err: &(dyn std::error::Error + 'static), - ) -> Result<(), value_bag::Error> { - self.0.visit_error(err).map_err(Error::into_value) - } - - #[cfg(feature = "kv_unstable_std")] - fn visit_borrowed_error( - &mut self, - err: &'v (dyn std::error::Error + 'static), - ) -> Result<(), value_bag::Error> { - self.0.visit_borrowed_error(err).map_err(Error::into_value) - } - } - - self.inner - .visit(&mut Visitor(visitor)) - .map_err(Error::from_value) + /// + /// When the `kv_serde` or `kv_sval` features are enabled, you can also + /// serialize a value using its `Serialize` or `Value` implementation. + pub fn visit(&self, visitor: impl VisitValue<'v>) -> Result<(), Error> { + inner::visit(&self.inner, visitor) } } @@ -379,7 +230,7 @@ impl<'v> fmt::Display for Value<'v> { } } -#[cfg(feature = "kv_unstable_serde")] +#[cfg(feature = "kv_serde")] impl<'v> serde::Serialize for Value<'v> { fn serialize(&self, s: S) -> Result where @@ -389,14 +240,14 @@ impl<'v> serde::Serialize for Value<'v> { } } -#[cfg(feature = "kv_unstable_sval")] +#[cfg(feature = "kv_sval")] impl<'v> sval::Value for Value<'v> { fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result { sval::Value::stream(&self.inner, stream) } } -#[cfg(feature = "kv_unstable_sval")] +#[cfg(feature = "kv_sval")] impl<'v> sval_ref::ValueRef<'v> for Value<'v> { fn stream_ref + ?Sized>(&self, stream: &mut S) -> sval::Result { sval_ref::ValueRef::stream_ref(&self.inner, stream) @@ -409,65 +260,15 @@ impl ToValue for str { } } -impl ToValue for u128 { - fn to_value(&self) -> Value { - Value::from(self) - } -} - -impl ToValue for i128 { - fn to_value(&self) -> Value { - Value::from(self) - } -} - -impl ToValue for std::num::NonZeroU128 { - fn to_value(&self) -> Value { - Value::from(self) - } -} - -impl ToValue for std::num::NonZeroI128 { - fn to_value(&self) -> Value { - Value::from(self) - } -} - impl<'v> From<&'v str> for Value<'v> { fn from(value: &'v str) -> Self { - Value::from_value_bag(value) - } -} - -impl<'v> From<&'v u128> for Value<'v> { - fn from(value: &'v u128) -> Self { - Value::from_value_bag(value) - } -} - -impl<'v> From<&'v i128> for Value<'v> { - fn from(value: &'v i128) -> Self { - Value::from_value_bag(value) - } -} - -impl<'v> From<&'v std::num::NonZeroU128> for Value<'v> { - fn from(v: &'v std::num::NonZeroU128) -> Value<'v> { - // SAFETY: `NonZeroU128` and `u128` have the same ABI - Value::from_value_bag(unsafe { &*(v as *const std::num::NonZeroU128 as *const u128) }) - } -} - -impl<'v> From<&'v std::num::NonZeroI128> for Value<'v> { - fn from(v: &'v std::num::NonZeroI128) -> Value<'v> { - // SAFETY: `NonZeroI128` and `i128` have the same ABI - Value::from_value_bag(unsafe { &*(v as *const std::num::NonZeroI128 as *const i128) }) + Value::from_inner(value) } } impl ToValue for () { fn to_value(&self) -> Value { - Value::from_value_bag(()) + Value::from_inner(()) } } @@ -478,7 +279,7 @@ where fn to_value(&self) -> Value { match *self { Some(ref value) => value.to_value(), - None => Value::from_value_bag(()), + None => Value::from_inner(()), } } } @@ -494,7 +295,13 @@ macro_rules! impl_to_value_primitive { impl<'v> From<$into_ty> for Value<'v> { fn from(value: $into_ty) -> Self { - Value::from_value_bag(value) + Value::from_inner(value) + } + } + + impl<'v> From<&'v $into_ty> for Value<'v> { + fn from(value: &'v $into_ty) -> Self { + Value::from_inner(*value) } } )* @@ -515,6 +322,12 @@ macro_rules! impl_to_value_nonzero_primitive { Value::from(value.get()) } } + + impl<'v> From<&'v std::num::$into_ty> for Value<'v> { + fn from(value: &'v std::num::$into_ty) -> Self { + Value::from(value.get()) + } + } )* }; } @@ -532,12 +345,14 @@ macro_rules! impl_value_to_primitive { } } -impl_to_value_primitive![usize, u8, u16, u32, u64, isize, i8, i16, i32, i64, f32, f64, char, bool,]; +impl_to_value_primitive![ + usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64, char, bool, +]; #[rustfmt::skip] impl_to_value_nonzero_primitive![ - NonZeroUsize, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, - NonZeroIsize, NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, + NonZeroUsize, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, + NonZeroIsize, NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, ]; impl_value_to_primitive![ @@ -559,7 +374,7 @@ impl_value_to_primitive![ impl<'v> Value<'v> { /// Try convert this value into an error. - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] pub fn to_borrowed_error(&self) -> Option<&(dyn std::error::Error + 'static)> { self.inner.to_borrowed_error() } @@ -570,7 +385,7 @@ impl<'v> Value<'v> { } } -#[cfg(feature = "kv_unstable_std")] +#[cfg(feature = "kv_std")] mod std_support { use std::borrow::Cow; use std::rc::Rc; @@ -631,20 +446,32 @@ mod std_support { } } -/// A visitor for a `Value`. +/// A visitor for a [`Value`]. /// -/// Also see [`Value`'s documentation on seralization]. +/// Also see [`Value`'s documentation on seralization]. Value visitors are a simple alternative +/// to a more fully-featured serialization framework like `serde` or `sval`. A value visitor +/// can differentiate primitive types through methods like [`VisitValue::visit_bool`] and +/// [`VisitValue::visit_str`], but more complex types like maps and sequences +/// will fallthrough to [`VisitValue::visit_any`]. +/// +/// If you're trying to serialize a value to a format like JSON, you can use either `serde` +/// or `sval` directly with the value. You don't need a visitor. /// /// [`Value`'s documentation on seralization]: Value#serialization -pub trait Visit<'v> { +pub trait VisitValue<'v> { /// Visit a `Value`. /// - /// This is the only required method on `Visit` and acts as a fallback for any + /// This is the only required method on `VisitValue` and acts as a fallback for any /// more specific methods that aren't overridden. /// The `Value` may be formatted using its `fmt::Debug` or `fmt::Display` implementation, /// or serialized using its `sval::Value` or `serde::Serialize` implementation. fn visit_any(&mut self, value: Value) -> Result<(), Error>; + /// Visit an empty value. + fn visit_null(&mut self) -> Result<(), Error> { + self.visit_any(Value::null()) + } + /// Visit an unsigned integer. fn visit_u64(&mut self, value: u64) -> Result<(), Error> { self.visit_any(value.into()) @@ -657,12 +484,12 @@ pub trait Visit<'v> { /// Visit a big unsigned integer. fn visit_u128(&mut self, value: u128) -> Result<(), Error> { - self.visit_any((&value).into()) + self.visit_any((value).into()) } /// Visit a big signed integer. fn visit_i128(&mut self, value: i128) -> Result<(), Error> { - self.visit_any((&value).into()) + self.visit_any((value).into()) } /// Visit a floating point. @@ -692,13 +519,13 @@ pub trait Visit<'v> { } /// Visit an error. - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] fn visit_error(&mut self, err: &(dyn std::error::Error + 'static)) -> Result<(), Error> { self.visit_any(Value::from_dyn_error(err)) } /// Visit an error. - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] fn visit_borrowed_error( &mut self, err: &'v (dyn std::error::Error + 'static), @@ -707,14 +534,18 @@ pub trait Visit<'v> { } } -impl<'a, 'v, T: ?Sized> Visit<'v> for &'a mut T +impl<'a, 'v, T: ?Sized> VisitValue<'v> for &'a mut T where - T: Visit<'v>, + T: VisitValue<'v>, { fn visit_any(&mut self, value: Value) -> Result<(), Error> { (**self).visit_any(value) } + fn visit_null(&mut self) -> Result<(), Error> { + (**self).visit_null() + } + fn visit_u64(&mut self, value: u64) -> Result<(), Error> { (**self).visit_u64(value) } @@ -751,12 +582,12 @@ where (**self).visit_char(value) } - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] fn visit_error(&mut self, err: &(dyn std::error::Error + 'static)) -> Result<(), Error> { (**self).visit_error(err) } - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] fn visit_borrowed_error( &mut self, err: &'v (dyn std::error::Error + 'static), @@ -765,13 +596,586 @@ where } } +#[cfg(feature = "value-bag")] +pub(in crate::kv) mod inner { + /** + An implementation of `Value` based on a library called `value_bag`. + + `value_bag` was written specifically for use in `log`'s value, but was split out when it outgrew + the codebase here. It's a general-purpose type-erasure library that handles mapping between + more fully-featured serialization frameworks. + */ + use super::*; + + pub use value_bag::ValueBag as Inner; + + pub use value_bag::Error; + + #[cfg(test)] + pub use value_bag::test::TestToken as Token; + + pub fn visit<'v>( + inner: &Inner<'v>, + visitor: impl VisitValue<'v>, + ) -> Result<(), crate::kv::Error> { + struct InnerVisitValue(V); + + impl<'v, V> value_bag::visit::Visit<'v> for InnerVisitValue + where + V: VisitValue<'v>, + { + fn visit_any(&mut self, value: value_bag::ValueBag) -> Result<(), Error> { + self.0 + .visit_any(Value { inner: value }) + .map_err(crate::kv::Error::into_value) + } + + fn visit_empty(&mut self) -> Result<(), Error> { + self.0.visit_null().map_err(crate::kv::Error::into_value) + } + + fn visit_u64(&mut self, value: u64) -> Result<(), Error> { + self.0 + .visit_u64(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_i64(&mut self, value: i64) -> Result<(), Error> { + self.0 + .visit_i64(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_u128(&mut self, value: u128) -> Result<(), Error> { + self.0 + .visit_u128(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_i128(&mut self, value: i128) -> Result<(), Error> { + self.0 + .visit_i128(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_f64(&mut self, value: f64) -> Result<(), Error> { + self.0 + .visit_f64(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_bool(&mut self, value: bool) -> Result<(), Error> { + self.0 + .visit_bool(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_str(&mut self, value: &str) -> Result<(), Error> { + self.0 + .visit_str(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), Error> { + self.0 + .visit_borrowed_str(value) + .map_err(crate::kv::Error::into_value) + } + + fn visit_char(&mut self, value: char) -> Result<(), Error> { + self.0 + .visit_char(value) + .map_err(crate::kv::Error::into_value) + } + + #[cfg(feature = "kv_std")] + fn visit_error( + &mut self, + err: &(dyn std::error::Error + 'static), + ) -> Result<(), Error> { + self.0 + .visit_error(err) + .map_err(crate::kv::Error::into_value) + } + + #[cfg(feature = "kv_std")] + fn visit_borrowed_error( + &mut self, + err: &'v (dyn std::error::Error + 'static), + ) -> Result<(), Error> { + self.0 + .visit_borrowed_error(err) + .map_err(crate::kv::Error::into_value) + } + } + + inner + .visit(&mut InnerVisitValue(visitor)) + .map_err(crate::kv::Error::from_value) + } +} + +#[cfg(not(feature = "value-bag"))] +pub(in crate::kv) mod inner { + /** + This is a dependency-free implementation of `Value` when there's no serialization frameworks involved. + In these simple cases a more fully featured solution like `value_bag` isn't needed, so we avoid pulling it in. + + There are a few things here that need to remain consistent with the `value_bag`-based implementation: + + 1. Conversions should always produce the same results. If a conversion here returns `Some`, then + the same `value_bag`-based conversion must also. Of particular note here are floats to ints; they're + based on the standard library's `TryInto` conversions, which need to be convert to `i32` or `u32`, + and then to `f64`. + 2. VisitValues should always be called in the same way. If a particular type of value calls `visit_i64`, + then the same `value_bag`-based visitor must also. + */ + use super::*; + + #[derive(Clone)] + pub enum Inner<'v> { + None, + Bool(bool), + Str(&'v str), + Char(char), + I64(i64), + U64(u64), + F64(f64), + I128(i128), + U128(u128), + Debug(&'v dyn fmt::Debug), + Display(&'v dyn fmt::Display), + } + + impl<'v> From<()> for Inner<'v> { + fn from(_: ()) -> Self { + Inner::None + } + } + + impl<'v> From for Inner<'v> { + fn from(v: bool) -> Self { + Inner::Bool(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: char) -> Self { + Inner::Char(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: f32) -> Self { + Inner::F64(v as f64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: f64) -> Self { + Inner::F64(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i8) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i16) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i32) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i64) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: isize) -> Self { + Inner::I64(v as i64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u8) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u16) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u32) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u64) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: usize) -> Self { + Inner::U64(v as u64) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: i128) -> Self { + Inner::I128(v) + } + } + + impl<'v> From for Inner<'v> { + fn from(v: u128) -> Self { + Inner::U128(v) + } + } + + impl<'v> From<&'v str> for Inner<'v> { + fn from(v: &'v str) -> Self { + Inner::Str(v) + } + } + + impl<'v> fmt::Debug for Inner<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Inner::None => fmt::Debug::fmt(&None::<()>, f), + Inner::Bool(v) => fmt::Debug::fmt(v, f), + Inner::Str(v) => fmt::Debug::fmt(v, f), + Inner::Char(v) => fmt::Debug::fmt(v, f), + Inner::I64(v) => fmt::Debug::fmt(v, f), + Inner::U64(v) => fmt::Debug::fmt(v, f), + Inner::F64(v) => fmt::Debug::fmt(v, f), + Inner::I128(v) => fmt::Debug::fmt(v, f), + Inner::U128(v) => fmt::Debug::fmt(v, f), + Inner::Debug(v) => fmt::Debug::fmt(v, f), + Inner::Display(v) => fmt::Display::fmt(v, f), + } + } + } + + impl<'v> fmt::Display for Inner<'v> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Inner::None => fmt::Debug::fmt(&None::<()>, f), + Inner::Bool(v) => fmt::Display::fmt(v, f), + Inner::Str(v) => fmt::Display::fmt(v, f), + Inner::Char(v) => fmt::Display::fmt(v, f), + Inner::I64(v) => fmt::Display::fmt(v, f), + Inner::U64(v) => fmt::Display::fmt(v, f), + Inner::F64(v) => fmt::Display::fmt(v, f), + Inner::I128(v) => fmt::Display::fmt(v, f), + Inner::U128(v) => fmt::Display::fmt(v, f), + Inner::Debug(v) => fmt::Debug::fmt(v, f), + Inner::Display(v) => fmt::Display::fmt(v, f), + } + } + } + + impl<'v> Inner<'v> { + pub fn from_debug(value: &'v T) -> Self { + Inner::Debug(value) + } + + pub fn from_display(value: &'v T) -> Self { + Inner::Display(value) + } + + pub fn from_dyn_debug(value: &'v dyn fmt::Debug) -> Self { + Inner::Debug(value) + } + + pub fn from_dyn_display(value: &'v dyn fmt::Display) -> Self { + Inner::Display(value) + } + + pub fn empty() -> Self { + Inner::None + } + + pub fn to_bool(&self) -> Option { + match self { + Inner::Bool(v) => Some(*v), + _ => None, + } + } + + pub fn to_char(&self) -> Option { + match self { + Inner::Char(v) => Some(*v), + _ => None, + } + } + + pub fn to_f64(&self) -> Option { + match self { + Inner::F64(v) => Some(*v), + Inner::I64(v) => { + let v: i32 = (*v).try_into().ok()?; + v.try_into().ok() + } + Inner::U64(v) => { + let v: u32 = (*v).try_into().ok()?; + v.try_into().ok() + } + Inner::I128(v) => { + let v: i32 = (*v).try_into().ok()?; + v.try_into().ok() + } + Inner::U128(v) => { + let v: u32 = (*v).try_into().ok()?; + v.try_into().ok() + } + _ => None, + } + } + + pub fn to_i64(&self) -> Option { + match self { + Inner::I64(v) => Some(*v), + Inner::U64(v) => (*v).try_into().ok(), + Inner::I128(v) => (*v).try_into().ok(), + Inner::U128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_u64(&self) -> Option { + match self { + Inner::U64(v) => Some(*v), + Inner::I64(v) => (*v).try_into().ok(), + Inner::I128(v) => (*v).try_into().ok(), + Inner::U128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_u128(&self) -> Option { + match self { + Inner::U128(v) => Some(*v), + Inner::I64(v) => (*v).try_into().ok(), + Inner::U64(v) => (*v).try_into().ok(), + Inner::I128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_i128(&self) -> Option { + match self { + Inner::I128(v) => Some(*v), + Inner::I64(v) => (*v).try_into().ok(), + Inner::U64(v) => (*v).try_into().ok(), + Inner::U128(v) => (*v).try_into().ok(), + _ => None, + } + } + + pub fn to_borrowed_str(&self) -> Option<&'v str> { + match self { + Inner::Str(v) => Some(v), + _ => None, + } + } + + #[cfg(test)] + pub fn to_test_token(&self) -> Token { + match self { + Inner::None => Token::None, + Inner::Bool(v) => Token::Bool(*v), + Inner::Str(v) => Token::Str(*v), + Inner::Char(v) => Token::Char(*v), + Inner::I64(v) => Token::I64(*v), + Inner::U64(v) => Token::U64(*v), + Inner::F64(v) => Token::F64(*v), + Inner::I128(_) => unimplemented!(), + Inner::U128(_) => unimplemented!(), + Inner::Debug(_) => unimplemented!(), + Inner::Display(_) => unimplemented!(), + } + } + } + + #[cfg(test)] + #[derive(Debug, PartialEq)] + pub enum Token<'v> { + None, + Bool(bool), + Char(char), + Str(&'v str), + F64(f64), + I64(i64), + U64(u64), + } + + pub fn visit<'v>( + inner: &Inner<'v>, + mut visitor: impl VisitValue<'v>, + ) -> Result<(), crate::kv::Error> { + match inner { + Inner::None => visitor.visit_null(), + Inner::Bool(v) => visitor.visit_bool(*v), + Inner::Str(v) => visitor.visit_borrowed_str(*v), + Inner::Char(v) => visitor.visit_char(*v), + Inner::I64(v) => visitor.visit_i64(*v), + Inner::U64(v) => visitor.visit_u64(*v), + Inner::F64(v) => visitor.visit_f64(*v), + Inner::I128(v) => visitor.visit_i128(*v), + Inner::U128(v) => visitor.visit_u128(*v), + Inner::Debug(v) => visitor.visit_any(Value::from_dyn_debug(*v)), + Inner::Display(v) => visitor.visit_any(Value::from_dyn_display(*v)), + } + } +} + +impl<'v> Value<'v> { + /// Get a value from a type implementing `std::fmt::Debug`. + #[cfg(feature = "kv_unstable")] + #[deprecated(note = "use `from_debug` instead")] + pub fn capture_debug(value: &'v T) -> Self + where + T: fmt::Debug + 'static, + { + Value::from_debug(value) + } + + /// Get a value from a type implementing `std::fmt::Display`. + #[cfg(feature = "kv_unstable")] + #[deprecated(note = "use `from_display` instead")] + pub fn capture_display(value: &'v T) -> Self + where + T: fmt::Display + 'static, + { + Value::from_display(value) + } + + /// Get a value from an error. + #[cfg(feature = "kv_unstable_std")] + #[deprecated(note = "use `from_dyn_error` instead")] + pub fn capture_error(err: &'v T) -> Self + where + T: std::error::Error + 'static, + { + Value::from_dyn_error(err) + } + + /// Get a value from a type implementing `serde::Serialize`. + #[cfg(feature = "kv_unstable_serde")] + #[deprecated(note = "use `from_serde` instead")] + pub fn capture_serde(value: &'v T) -> Self + where + T: serde::Serialize + 'static, + { + Value::from_serde(value) + } + + /// Get a value from a type implementing `sval::Value`. + #[cfg(feature = "kv_unstable_sval")] + #[deprecated(note = "use `from_sval` instead")] + pub fn capture_sval(value: &'v T) -> Self + where + T: sval::Value + 'static, + { + Value::from_sval(value) + } + + /// Check whether this value can be downcast to `T`. + #[cfg(feature = "kv_unstable")] + #[deprecated( + note = "downcasting has been removed; log an issue at https://github.com/rust-lang/log/issues if this is something you rely on" + )] + pub fn is(&self) -> bool { + false + } + + /// Try downcast this value to `T`. + #[cfg(feature = "kv_unstable")] + #[deprecated( + note = "downcasting has been removed; log an issue at https://github.com/rust-lang/log/issues if this is something you rely on" + )] + pub fn downcast_ref(&self) -> Option<&T> { + None + } +} + +// NOTE: Deprecated; but aliases can't carry this attribute +#[cfg(feature = "kv_unstable")] +pub use VisitValue as Visit; + +/// Get a value from a type implementing `std::fmt::Debug`. +#[cfg(feature = "kv_unstable")] +#[deprecated(note = "use the `key:? = value` macro syntax instead")] +#[macro_export] +macro_rules! as_debug { + ($capture:expr) => { + $crate::kv::Value::from_debug(&$capture) + }; +} + +/// Get a value from a type implementing `std::fmt::Display`. +#[cfg(feature = "kv_unstable")] +#[deprecated(note = "use the `key:% = value` macro syntax instead")] +#[macro_export] +macro_rules! as_display { + ($capture:expr) => { + $crate::kv::Value::from_display(&$capture) + }; +} + +/// Get a value from an error. +#[cfg(feature = "kv_unstable_std")] +#[deprecated(note = "use the `key:err = value` macro syntax instead")] +#[macro_export] +macro_rules! as_error { + ($capture:expr) => { + $crate::kv::Value::from_dyn_error(&$capture) + }; +} + +#[cfg(feature = "kv_unstable_serde")] +#[deprecated(note = "use the `key:serde = value` macro syntax instead")] +/// Get a value from a type implementing `serde::Serialize`. +#[macro_export] +macro_rules! as_serde { + ($capture:expr) => { + $crate::kv::Value::from_serde(&$capture) + }; +} + +/// Get a value from a type implementing `sval::Value`. +#[cfg(feature = "kv_unstable_sval")] +#[deprecated(note = "use the `key:sval = value` macro syntax instead")] +#[macro_export] +macro_rules! as_sval { + ($capture:expr) => { + $crate::kv::Value::from_sval(&$capture) + }; +} + #[cfg(test)] pub(crate) mod tests { use super::*; - pub(crate) use value_bag::test::TestToken as Token; impl<'v> Value<'v> { - pub(crate) fn to_token(&self) -> Token { + pub(crate) fn to_token(&self) -> inner::Token { self.inner.to_test_token() } } @@ -824,40 +1228,6 @@ pub(crate) mod tests { vec![Value::from('a'), Value::from('⛰')].into_iter() } - #[test] - fn test_capture_fmt() { - assert_eq!(Some(42u64), Value::capture_display(&42).to_u64()); - assert_eq!(Some(42u64), Value::capture_debug(&42).to_u64()); - - assert!(Value::from_display(&42).to_u64().is_none()); - assert!(Value::from_debug(&42).to_u64().is_none()); - } - - #[cfg(feature = "kv_unstable_std")] - #[test] - fn test_capture_error() { - let err = std::io::Error::from(std::io::ErrorKind::Other); - - assert!(Value::capture_error(&err).to_borrowed_error().is_some()); - assert!(Value::from_dyn_error(&err).to_borrowed_error().is_some()); - } - - #[cfg(feature = "kv_unstable_serde")] - #[test] - fn test_capture_serde() { - assert_eq!(Some(42u64), Value::capture_serde(&42).to_u64()); - - assert_eq!(Some(42u64), Value::from_serde(&42).to_u64()); - } - - #[cfg(feature = "kv_unstable_sval")] - #[test] - fn test_capture_sval() { - assert_eq!(Some(42u64), Value::capture_sval(&42).to_u64()); - - assert_eq!(Some(42u64), Value::from_sval(&42).to_u64()); - } - #[test] fn test_to_value_display() { assert_eq!(42u64.to_value().to_string(), "42"); @@ -873,18 +1243,18 @@ pub(crate) mod tests { #[test] fn test_to_value_structured() { - assert_eq!(42u64.to_value().to_token(), Token::U64(42)); - assert_eq!(42i64.to_value().to_token(), Token::I64(42)); - assert_eq!(42.01f64.to_value().to_token(), Token::F64(42.01)); - assert_eq!(true.to_value().to_token(), Token::Bool(true)); - assert_eq!('a'.to_value().to_token(), Token::Char('a')); + assert_eq!(42u64.to_value().to_token(), inner::Token::U64(42)); + assert_eq!(42i64.to_value().to_token(), inner::Token::I64(42)); + assert_eq!(42.01f64.to_value().to_token(), inner::Token::F64(42.01)); + assert_eq!(true.to_value().to_token(), inner::Token::Bool(true)); + assert_eq!('a'.to_value().to_token(), inner::Token::Char('a')); assert_eq!( "a loong string".to_value().to_token(), - Token::Str("a loong string".into()) + inner::Token::Str("a loong string".into()) ); - assert_eq!(Some(true).to_value().to_token(), Token::Bool(true)); - assert_eq!(().to_value().to_token(), Token::None); - assert_eq!(None::.to_value().to_token(), Token::None); + assert_eq!(Some(true).to_value().to_token(), inner::Token::Bool(true)); + assert_eq!(().to_value().to_token(), inner::Token::None); + assert_eq!(None::.to_value().to_token(), inner::Token::None); } #[test] @@ -909,12 +1279,22 @@ pub(crate) mod tests { } } + #[test] + fn test_to_float() { + // Only integers from i32::MIN..=u32::MAX can be converted into floats + assert!(Value::from(i32::MIN).to_f64().is_some()); + assert!(Value::from(u32::MAX).to_f64().is_some()); + + assert!(Value::from((i32::MIN as i64) - 1).to_f64().is_none()); + assert!(Value::from((u32::MAX as u64) + 1).to_f64().is_none()); + } + #[test] fn test_to_cow_str() { for v in str() { assert!(v.to_borrowed_str().is_some()); - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] assert!(v.to_cow_str().is_some()); } @@ -923,13 +1303,13 @@ pub(crate) mod tests { assert!(v.to_borrowed_str().is_some()); - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] assert!(v.to_cow_str().is_some()); for v in unsigned().chain(signed()).chain(float()).chain(bool()) { assert!(v.to_borrowed_str().is_none()); - #[cfg(feature = "kv_unstable_std")] + #[cfg(feature = "kv_std")] assert!(v.to_cow_str().is_none()); } } @@ -966,22 +1346,11 @@ pub(crate) mod tests { } } - #[test] - fn test_downcast_ref() { - #[derive(Debug)] - struct Foo(u64); - - let v = Value::capture_debug(&Foo(42)); - - assert!(v.is::()); - assert_eq!(42u64, v.downcast_ref::().expect("invalid downcast").0); - } - #[test] fn test_visit_integer() { struct Extract(Option); - impl<'v> Visit<'v> for Extract { + impl<'v> VisitValue<'v> for Extract { fn visit_any(&mut self, value: Value) -> Result<(), Error> { unimplemented!("unexpected value: {value:?}") } @@ -1003,7 +1372,7 @@ pub(crate) mod tests { fn test_visit_borrowed_str() { struct Extract<'v>(Option<&'v str>); - impl<'v> Visit<'v> for Extract<'v> { + impl<'v> VisitValue<'v> for Extract<'v> { fn visit_any(&mut self, value: Value) -> Result<(), Error> { unimplemented!("unexpected value: {value:?}") } diff --git a/src/lib.rs b/src/lib.rs index 6a3a8ca98..505d24961 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ //! //! ## Structured logging //! -//! If you enable the `kv_unstable` feature you can associate structured values +//! If you enable the `kv` feature you can associate structured values //! with your log records. If we take the example from before, we can include //! some additional context besides what's in the formatted message: //! @@ -97,31 +97,33 @@ //! # #[derive(Debug, Serialize)] pub struct Yak(String); //! # impl Yak { fn shave(&mut self, _: u32) {} } //! # fn find_a_razor() -> Result { Ok(1) } -//! # #[cfg(feature = "kv_unstable_serde")] +//! # #[cfg(feature = "kv_serde")] //! # fn main() { -//! use log::{info, warn, as_serde, as_error}; +//! use log::{info, warn}; //! //! pub fn shave_the_yak(yak: &mut Yak) { -//! info!(target: "yak_events", yak = as_serde!(yak); "Commencing yak shaving"); +//! info!(target: "yak_events", yak:serde; "Commencing yak shaving"); //! //! loop { //! match find_a_razor() { //! Ok(razor) => { -//! info!(razor = razor; "Razor located"); +//! info!(razor; "Razor located"); //! yak.shave(razor); //! break; //! } -//! Err(err) => { -//! warn!(err = as_error!(err); "Unable to locate a razor, retrying"); +//! Err(e) => { +//! warn!(e:err; "Unable to locate a razor, retrying"); //! } //! } //! } //! } //! # } -//! # #[cfg(not(feature = "kv_unstable_serde"))] +//! # #[cfg(not(feature = "kv_serde"))] //! # fn main() {} //! ``` //! +//! See the [`kv`] module documentation for more details. +//! //! # Available logging implementations //! //! In order to produce log output executables have to use @@ -353,7 +355,7 @@ use std::{cmp, fmt, mem}; mod macros; mod serde; -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] pub mod kv; #[cfg(target_has_atomic = "ptr")] @@ -721,7 +723,7 @@ pub struct Record<'a> { module_path: Option>, file: Option>, line: Option, - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] key_values: KeyValues<'a>, } @@ -729,11 +731,11 @@ pub struct Record<'a> { // `#[derive(Debug)]` on `Record`. It also // provides a useful `Debug` implementation for // the underlying `Source`. -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] #[derive(Clone)] struct KeyValues<'a>(&'a dyn kv::Source); -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] impl<'a> fmt::Debug for KeyValues<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut visitor = f.debug_map(); @@ -810,14 +812,14 @@ impl<'a> Record<'a> { } /// The structured key-value pairs associated with the message. - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] #[inline] pub fn key_values(&self) -> &dyn kv::Source { self.key_values.0 } /// Create a new [`RecordBuilder`](struct.RecordBuilder.html) based on this record. - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] #[inline] pub fn to_builder(&self) -> RecordBuilder { RecordBuilder { @@ -902,7 +904,7 @@ impl<'a> RecordBuilder<'a> { module_path: None, file: None, line: None, - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] key_values: KeyValues(&None::<(kv::Key, kv::Value)>), }, } @@ -972,7 +974,7 @@ impl<'a> RecordBuilder<'a> { } /// Set [`key_values`](struct.Record.html#method.key_values) - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] #[inline] pub fn key_values(&mut self, kvs: &'a dyn kv::Source) -> &mut RecordBuilder<'a> { self.record.key_values = KeyValues(kvs); @@ -1755,16 +1757,16 @@ mod tests { } #[test] - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] fn test_record_key_values_builder() { use super::Record; - use crate::kv::{self, Visitor}; + use crate::kv::{self, VisitSource}; - struct TestVisitor { + struct TestVisitSource { seen_pairs: usize, } - impl<'kvs> Visitor<'kvs> for TestVisitor { + impl<'kvs> VisitSource<'kvs> for TestVisitSource { fn visit_pair( &mut self, _: kv::Key<'kvs>, @@ -1778,7 +1780,7 @@ mod tests { let kvs: &[(&str, i32)] = &[("a", 1), ("b", 2)]; let record_test = Record::builder().key_values(&kvs).build(); - let mut visitor = TestVisitor { seen_pairs: 0 }; + let mut visitor = TestVisitSource { seen_pairs: 0 }; record_test.key_values().visit(&mut visitor).unwrap(); @@ -1786,7 +1788,7 @@ mod tests { } #[test] - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] fn test_record_key_values_get_coerce() { use super::Record; diff --git a/src/macros.rs b/src/macros.rs index 44945f0d9..48a7447ef 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -29,8 +29,8 @@ /// ``` #[macro_export] macro_rules! log { - // log!(target: "my_target", Level::Info, key1 = 42, key2 = true; "a {} event", "log"); - (target: $target:expr, $lvl:expr, $($key:tt = $value:expr),+; $($arg:tt)+) => ({ + // log!(target: "my_target", Level::Info, key1:? = 42, key2 = true; "a {} event", "log"); + (target: $target:expr, $lvl:expr, $($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => ({ let lvl = $lvl; if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { $crate::__private_api::log::<&_>( @@ -38,7 +38,7 @@ macro_rules! log { lvl, &($target, $crate::__private_api::module_path!(), $crate::__private_api::file!()), $crate::__private_api::line!(), - &[$(($crate::__log_key!($key), &$value)),+] + &[$(($crate::__log_key!($key), $crate::__log_value!($key $(:$capture)* = $($value)*))),+] ); } }); @@ -226,8 +226,12 @@ macro_rules! log_enabled { }; } +// These macros use a pattern of #[cfg]s to produce nicer error +// messages when log features aren't available + #[doc(hidden)] #[macro_export] +#[cfg(feature = "kv")] macro_rules! __log_key { // key1 = 42 ($($args:ident)*) => { @@ -238,3 +242,128 @@ macro_rules! __log_key { $($args)* }; } + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "kv"))] +macro_rules! __log_key { + ($($args:tt)*) => { + compile_error!("key value support requires the `kv` feature of `log`") + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv")] +macro_rules! __log_value { + // Entrypoint + ($key:tt = $args:expr) => { + $crate::__log_value!(($args):value) + }; + ($key:tt :$capture:tt = $args:expr) => { + $crate::__log_value!(($args):$capture) + }; + ($key:ident =) => { + $crate::__log_value!(($key):value) + }; + ($key:ident :$capture:tt =) => { + $crate::__log_value!(($key):$capture) + }; + // ToValue + (($args:expr):value) => { + $crate::__private_api::capture_to_value(&&$args) + }; + // Debug + (($args:expr):?) => { + $crate::__private_api::capture_debug(&&$args) + }; + (($args:expr):debug) => { + $crate::__private_api::capture_debug(&&$args) + }; + // Display + (($args:expr):%) => { + $crate::__private_api::capture_display(&&$args) + }; + (($args:expr):display) => { + $crate::__private_api::capture_display(&&$args) + }; + //Error + (($args:expr):err) => { + $crate::__log_value_error!($args) + }; + // sval::Value + (($args:expr):sval) => { + $crate::__log_value_sval!($args) + }; + // serde::Serialize + (($args:expr):serde) => { + $crate::__log_value_serde!($args) + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "kv"))] +macro_rules! __log_value { + ($($args:tt)*) => { + compile_error!("key value support requires the `kv` feature of `log`") + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv_sval")] +macro_rules! __log_value_sval { + ($args:expr) => { + $crate::__private_api::capture_sval(&&$args) + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "kv_sval"))] +macro_rules! __log_value_sval { + ($args:expr) => { + compile_error!("capturing values as `sval::Value` requites the `kv_sval` feature of `log`") + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv_serde")] +macro_rules! __log_value_serde { + ($args:expr) => { + $crate::__private_api::capture_serde(&&$args) + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "kv_serde"))] +macro_rules! __log_value_serde { + ($args:expr) => { + compile_error!( + "capturing values as `serde::Serialize` requites the `kv_serde` feature of `log`" + ) + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "kv_std")] +macro_rules! __log_value_error { + ($args:expr) => { + $crate::__private_api::capture_error(&$args) + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "kv_std"))] +macro_rules! __log_value_error { + ($args:expr) => { + compile_error!( + "capturing values as `std::error::Error` requites the `kv_std` feature of `log`" + ) + }; +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 88f37b5d4..8754bb16a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -7,6 +7,10 @@ build = "src/build.rs" [features] std = ["log/std"] +kv = ["log/kv"] +kv_std = ["log/kv_std"] +kv_sval = ["log/kv_sval"] +kv_serde = ["log/kv_serde"] [dependencies.log] path = ".." diff --git a/tests/macros.rs b/tests/macros.rs index 3afcf88da..20da6ac44 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -107,7 +107,7 @@ fn expr() { } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] fn kv_no_args() { for lvl in log::Level::iter() { log!(target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello"); @@ -121,7 +121,7 @@ fn kv_no_args() { } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] fn kv_expr_args() { for lvl in log::Level::iter() { log!(target: "my_target", lvl, cat_math = { let mut x = 0; x += 1; x + 1 }; "hello"); @@ -136,7 +136,7 @@ fn kv_expr_args() { } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] fn kv_anonymous_args() { for lvl in log::Level::iter() { log!(target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {}", "world"); @@ -151,7 +151,7 @@ fn kv_anonymous_args() { } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] fn kv_named_args() { for lvl in log::Level::iter() { log!(target: "my_target", lvl, cat_1 = "chashu", cat_2 = "nori", cat_count = 2; "hello {world}", world = "world"); @@ -166,7 +166,16 @@ fn kv_named_args() { } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] +fn kv_ident() { + let cat_1 = "chashu"; + let cat_2 = "nori"; + + all_log_macros!(cat_1, cat_2:%, cat_count = 2; "hello {world}", world = "world"); +} + +#[test] +#[cfg(feature = "kv")] fn kv_expr_context() { match "chashu" { cat_1 => { @@ -196,14 +205,14 @@ fn implicit_named_args() { all_log_macros!(target: "my_target", "hello {world}"); all_log_macros!(target: "my_target", "hello {world}",); - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] all_log_macros!(target = "my_target"; "hello {world}"); - #[cfg(feature = "kv_unstable")] + #[cfg(feature = "kv")] all_log_macros!(target = "my_target"; "hello {world}",); } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] fn kv_implicit_named_args() { let world = "world"; @@ -219,7 +228,7 @@ fn kv_implicit_named_args() { } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] fn kv_string_keys() { for lvl in log::Level::iter() { log!(target: "my_target", lvl, "also dogs" = "Fílos", "key/that-can't/be/an/ident" = "hi"; "hello {world}", world = "world"); @@ -229,7 +238,7 @@ fn kv_string_keys() { } #[test] -#[cfg(feature = "kv_unstable")] +#[cfg(feature = "kv")] fn kv_common_value_types() { all_log_macros!( u8 = 42u8, @@ -250,6 +259,53 @@ fn kv_common_value_types() { ); } +#[test] +#[cfg(feature = "kv")] +fn kv_debug() { + all_log_macros!( + a:? = 42, + b:debug = 42; + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv")] +fn kv_display() { + all_log_macros!( + a:% = 42, + b:display = 42; + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv_std")] +fn kv_error() { + all_log_macros!( + a:err = std::io::Error::new(std::io::ErrorKind::Other, "an error"); + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv_sval")] +fn kv_sval() { + all_log_macros!( + a:sval = 42; + "hello world" + ); +} + +#[test] +#[cfg(feature = "kv_serde")] +fn kv_serde() { + all_log_macros!( + a:serde = 42; + "hello world" + ); +} + /// Some and None (from Option) are used in the macros. #[derive(Debug)] enum Type {