Skip to main content

context_logger/
records.rs

1use std::{borrow::Cow, collections::HashMap};
2
3use crate::LogValue;
4
5pub type LogRecordsIter<'a> = std::collections::hash_map::Iter<'a, Cow<'static, str>, LogValue>;
6pub type LogRecordsIntoIter = std::collections::hash_map::IntoIter<Cow<'static, str>, LogValue>;
7pub type LogRecord = (Cow<'static, str>, LogValue);
8pub type LogRecordRef<'a> = (&'a Cow<'static, str>, &'a LogValue);
9
10/// A set of records that can be attached to a logging scope.
11///
12/// [`LogRecords`] represents a set of key-value pairs that can be
13/// added to log messages when the log context scope is active.
14///
15/// # Ordering
16///
17/// The order in which records appear is **not guaranteed**. Do not rely on any specific
18/// ordering of keys.
19#[derive(Debug, Clone, Default)]
20pub struct LogRecords(pub(crate) HashMap<Cow<'static, str>, LogValue>);
21
22impl LogRecords {
23    /// Creates a new, empty set of records.
24    #[must_use]
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// Inserts a key-value record into this collection, returning the collection for chained calls.
30    ///
31    /// This method takes ownership of `self`, so it can be used as part of a
32    /// builder-style chain:
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// use context_logger::LogRecords;
38    ///
39    /// let records = LogRecords::new()
40    ///       .with_record("user_id", "user-123")
41    ///       .with_record("request_id", 42);
42    /// ```
43    #[must_use]
44    pub fn with_record(
45        mut self,
46        key: impl Into<Cow<'static, str>>,
47        value: impl Into<LogValue>,
48    ) -> Self {
49        self.insert(key, value);
50        self
51    }
52
53    /// Inserts a key-value record into this collection.
54    ///
55    /// Unlike [`with_record`](LogRecords::with_record), this method borrows `self` and
56    /// returns a mutable reference, allowing it to be used when chaining with other methods
57    /// that require borrowing.
58    ///
59    /// # Examples
60    ///
61    /// ```
62    /// use context_logger::LogRecords;
63    ///
64    /// let mut records = LogRecords::new();
65    /// records
66    ///     .insert("user_id", "user-123")
67    ///     .insert("request_id", 42);
68    /// ```
69    pub fn insert(
70        &mut self,
71        key: impl Into<Cow<'static, str>>,
72        value: impl Into<LogValue>,
73    ) -> &mut Self {
74        self.0.insert(key.into(), value.into());
75        self
76    }
77
78    /// Merges this collection with the records from another collection.
79    ///
80    /// This method borrows `self` and returns a mutable reference, allowing it
81    /// to be used when chaining with other methods that require borrowing.
82    ///
83    /// # Merging policy
84    ///
85    /// Keys in this collection with duplicate names will be overwritten by keys from the
86    /// provided collection. The order of keys in the resulting collection is undefined.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use context_logger::LogRecords;
92    ///
93    /// # let other_records = LogRecords::new();
94    /// let mut records = LogRecords::new();
95    /// records
96    ///     .insert("user_id", "Alice")
97    ///     .merge_with(other_records)
98    ///     .insert("request_id", 42);
99    /// ```
100    pub fn merge_with(&mut self, other: impl IntoIterator<Item = LogRecord>) -> &mut Self {
101        self.0.extend(other);
102        self
103    }
104
105    /// Returns an iterator over the records in this collection.
106    #[must_use]
107    pub fn iter(&self) -> LogRecordsIter<'_> {
108        self.0.iter()
109    }
110
111    /// Returns `true` if this collection contains no records.
112    #[must_use]
113    pub fn is_empty(&self) -> bool {
114        self.0.is_empty()
115    }
116}
117
118impl<'a> IntoIterator for &'a LogRecords {
119    type Item = LogRecordRef<'a>;
120    type IntoIter = LogRecordsIter<'a>;
121
122    fn into_iter(self) -> Self::IntoIter {
123        self.iter()
124    }
125}
126
127impl IntoIterator for LogRecords {
128    type Item = LogRecord;
129    type IntoIter = LogRecordsIntoIter;
130
131    fn into_iter(self) -> Self::IntoIter {
132        self.0.into_iter()
133    }
134}
135
136impl Extend<LogRecord> for LogRecords {
137    fn extend<I: IntoIterator<Item = LogRecord>>(&mut self, iter: I) {
138        self.0.extend(iter);
139    }
140}
141
142impl FromIterator<LogRecord> for LogRecords {
143    fn from_iter<T: IntoIterator<Item = LogRecord>>(iter: T) -> Self {
144        Self(HashMap::from_iter(iter))
145    }
146}
147
148#[cfg(test)]
149impl LogRecords {
150    /// Returns a reference to the value associated with the given key, if it exists.
151    pub(crate) fn find(&self, key: impl AsRef<str>) -> Option<&LogValue> {
152        self.0.get(&Cow::Owned(key.as_ref().to_owned()))
153    }
154}
155
156#[cfg(test)]
157impl std::ops::Index<&str> for LogRecords {
158    type Output = LogValue;
159
160    fn index(&self, index: &str) -> &Self::Output {
161        self.0
162            .get(&Cow::Owned(index.to_owned()))
163            .expect("No record found for the given key")
164    }
165}