From 05119e1886b92b886e945b890b029e522d69fcea Mon Sep 17 00:00:00 2001 From: KodrAus Date: Mon, 29 Jan 2024 20:45:17 +1000 Subject: [PATCH] fill in some more doc examples --- Cargo.toml | 1 + src/kv/key.rs | 2 +- src/kv/mod.rs | 166 +++++++++++++++++++++++++++++++++++++++++------ src/kv/source.rs | 70 ++++++++++++++++---- src/kv/value.rs | 2 +- 5 files changed, 206 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99192dc5d..ef0e3eb7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ value-bag = { version = "1.4", optional = true, default-features = false } [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" } diff --git a/src/kv/key.rs b/src/kv/key.rs index e53a64a6f..fb58008c8 100644 --- a/src/kv/key.rs +++ b/src/kv/key.rs @@ -30,7 +30,7 @@ impl ToKey for str { } } -/// A key in a user-defined attribute. +/// 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)] diff --git a/src/kv/mod.rs b/src/kv/mod.rs index deaf4615f..650fc6da2 100644 --- a/src/kv/mod.rs +++ b/src/kv/mod.rs @@ -19,62 +19,186 @@ //! 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 [`LogRecord`]. -//! Each attribute is 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 +//! 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 attributes to log records +//! ## Adding key-values to log records //! -//! Attributes appear after the message format in the `log!` macros: +//! Key-values appear after the message format in the `log!` macros: //! //! ``` //! .. //! ``` //! -//! ## Working with attributes on log records +//! ## Working with key-values on log records //! -//! Use the [`LogRecord::source`] method to access user-defined attributes. -//! Individual attributes can be pulled from the source: +//! Use the [`LogRecord::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!("Something of interest"; a = 1); +//! let a: Value = record.key_values().get(Key::from("a")).unwrap(); +//! # Ok(()) +//! # } //! ``` //! -//! This is convenient when an attribute of interest is known in advance. -//! All attributes can also be enumerated using a [`Visitor`]: +//! All key-values can also be enumerated using a [`source::Visitor`]: //! //! ``` -//! .. +//! # fn main() -> Result<(), log::kv::Error> { +//! # let record = log::Record::builder().key_values(&[("a", 1), ("b", 2), ("c", 3)]).build(); +//! use std::collections::BTreeMap; +//! +//! use log::kv::{self, Source, Key, Value, source::Visitor}; +//! +//! struct Collect<'kvs>(BTreeMap, Value<'kvs>>); +//! +//! impl<'kvs> Visitor<'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()); +//! +//! // info!("Something of interest"; a = 1, b = 2, c = 3); +//! 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 in attributes have methods for conversions to common types: +//! [`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!("Something of interest"; a = 1); +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!(1, a.to_i64().unwrap()); +//! # Ok(()) +//! # } //! ``` //! -//! Values also have their own [`value::Visitor`] type: +//! Values also have their own [`value::Visitor`] type. Visitors are a lightweight +//! API for working with primitives types: //! //! ``` -//! .. +//! # fn main() -> Result<(), log::kv::Error> { +//! use log::kv::{self, Source, Key, value::Visitor}; +//! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); +//! +//! struct IsNumeric(bool); +//! +//! impl<'kvs> Visitor<'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!("Something of interest"; a = 1); +//! 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(()) +//! # } //! ``` //! -//! Visitors on values are lightweight and suitable for detecting primitive types. -//! To serialize a value, you can also use either `serde` or `sval`: +//! 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!("Something of interest"; a = data); +//! 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. //! //! 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!("Something of interest"; a = data); +//! let a = record.key_values().get(Key::from("a")).unwrap(); +//! +//! assert_eq!("Data { a: 1, b: true, c: \"Some data\" }", format!("{a:?}")); //! ``` mod error; diff --git a/src/kv/source.rs b/src/kv/source.rs index 2f62e1999..69544162d 100644 --- a/src/kv/source.rs +++ b/src/kv/source.rs @@ -1,34 +1,63 @@ -//! Sources for user-defined attributes. +//! Sources for key-values. //! //! This module defines the [`Source`] type and supporting APIs for -//! working with collections of attributes. +//! working with collections of key-values. use crate::kv::{Error, Key, ToKey, ToValue, Value}; use std::fmt; -/// A source of user-defined attributes. +/// 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 /// 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 attributes in a source: +/// Enumerating the key-values in a source: /// /// ``` -/// .. +/// # fn main() -> Result<(), log::kv::Error> { +/// use log::kv::{self, Source, Key, Value, source::Visitor}; +/// +/// // A `Visitor` that prints all key-values +/// // Visitors are fed the key-value pairs of each key-values +/// struct Printer; +/// +/// impl<'kvs> Visitor<'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 `Visitor` to a `Source` to visit it +/// source.visit(&mut Printer)?; +/// # Ok(()) +/// # } /// ``` pub trait Source { - /// Visit attributes. + /// Visit key-values. /// - /// A source doesn't have to guarantee any ordering or uniqueness of attributes. + /// 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 attributes. + /// even if there are more key-values. /// /// # Implementation notes /// - /// A source should yield the same attributes 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>; @@ -45,14 +74,14 @@ pub trait Source { get_default(self, key) } - /// Count the number of attributes that can be visited. + /// Count the number of key-values that can be visited. /// /// # Implementation notes /// - /// A source that knows the number of attributes 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 attributes + /// A subsequent call to `visit` should yield the same number of key-values /// to the visitor, unless that visitor fails part way through. fn count(&self) -> usize { count_default(self) @@ -165,6 +194,23 @@ where } } +impl Source for [S; N] +where + S: Source, +{ + fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'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, diff --git a/src/kv/value.rs b/src/kv/value.rs index 67b5427ea..1abc01bde 100644 --- a/src/kv/value.rs +++ b/src/kv/value.rs @@ -30,7 +30,7 @@ impl<'v> ToValue for Value<'v> { } } -/// A value in a user-defined attribute. +/// A value in a key-value. /// /// Values are an anonymous bag containing some structured datum. ///