Skip to main content

log_io/
value.rs

1//! Field values.
2//!
3//! [`Value`] is a borrowed sum type for the data attached to a log
4//! [`crate::Field`]. Borrowing keeps the fast path allocation-free: a
5//! record built from `Value`s lives on the stack and points at data the
6//! caller already owns.
7
8use core::fmt;
9
10/// A field value attached to a log record.
11///
12/// Every variant borrows (or copies) its data, so constructing a
13/// `Value` never allocates. The lazy variants ([`Self::Display`],
14/// [`Self::Debug`], [`Self::Error`]) defer formatting until a sink
15/// actually serializes the record, so a record rejected by the filter
16/// never pays for them.
17///
18/// The enum is `#[non_exhaustive]`: new variants may be added in minor
19/// releases, so `match` statements outside this crate need a wildcard
20/// arm.
21///
22/// # Serialization
23///
24/// | Variant | JSON | logfmt / human |
25/// |---|---|---|
26/// | `Null` | `null` | empty / `<null>` |
27/// | `Bool` | `true` / `false` | `true` / `false` |
28/// | `I64`, `U64`, `I128`, `U128` | number | decimal |
29/// | `F64` (finite) | number, always with `.` or exponent (`1.0`, `1e300`) | same text |
30/// | `F64` (NaN / infinite) | `null` | `NaN`, `inf`, `-inf` |
31/// | `Str`, `Char` | escaped string | escaped, quoted when needed |
32/// | `Bytes` | lowercase hex string | lowercase hex |
33/// | `Display`, `Debug` | escaped string of the rendered text | rendered text, escaped |
34/// | `Error` | the error, then `: source` for each source, as a string | same text |
35///
36/// # Examples
37///
38#[cfg_attr(all(feature = "std"), doc = "```")]
39#[cfg_attr(not(all(feature = "std")), doc = "```ignore")]
40/// use log_io::Value;
41///
42/// let port: Value<'_> = 8080_u16.into();
43/// assert_eq!(port, Value::U64(8080));
44///
45/// let name = String::from("alice");
46/// let v: Value<'_> = (&name).into();
47/// assert_eq!(v, Value::Str("alice"));
48///
49/// // Lazy formatting: nothing is rendered until a sink serializes it.
50/// let addr = std::net::Ipv4Addr::LOCALHOST;
51/// let v = Value::Display(&addr);
52/// assert_eq!(v.to_string(), "127.0.0.1");
53/// ```
54#[derive(Clone, Copy, Default)]
55#[non_exhaustive]
56pub enum Value<'a> {
57    /// Absence of a value.
58    #[default]
59    Null,
60    /// A boolean.
61    Bool(bool),
62    /// A signed 64-bit integer.
63    I64(i64),
64    /// An unsigned 64-bit integer.
65    U64(u64),
66    /// A signed 128-bit integer. JSON consumers that parse numbers as
67    /// IEEE doubles lose precision above 2^53.
68    I128(i128),
69    /// An unsigned 128-bit integer. Same precision caveat as
70    /// [`Self::I128`].
71    U128(u128),
72    /// A 64-bit float. Finite values are written in Rust's
73    /// round-trippable `{:?}` form, which always carries a decimal
74    /// point or an exponent so consumers never mistake `1.0` for an
75    /// integer. `NaN` and infinities become JSON `null` because JSON
76    /// has no representation for them.
77    F64(f64),
78    /// A borrowed string slice.
79    Str(&'a str),
80    /// A single character, serialized as a one-character string.
81    Char(char),
82    /// Raw bytes, serialized as lowercase hexadecimal.
83    Bytes(&'a [u8]),
84    /// A value rendered through its [`fmt::Display`] implementation
85    /// when (and only when) the record is serialized.
86    Display(&'a dyn fmt::Display),
87    /// A value rendered through its [`fmt::Debug`] implementation
88    /// when (and only when) the record is serialized.
89    Debug(&'a dyn fmt::Debug),
90    /// An error, rendered as its `Display` text followed by
91    /// `": <source>"` for every error in its [`source`] chain.
92    ///
93    /// [`source`]: core::error::Error::source
94    Error(&'a (dyn core::error::Error + 'static)),
95}
96
97impl Value<'_> {
98    /// Returns `true` if this value is [`Self::Null`].
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use log_io::Value;
104    /// assert!(Value::Null.is_null());
105    /// assert!(!Value::Bool(false).is_null());
106    /// ```
107    #[must_use]
108    pub const fn is_null(&self) -> bool {
109        matches!(self, Self::Null)
110    }
111
112    /// The static name of the variant, for diagnostics.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use log_io::Value;
118    /// assert_eq!(Value::U64(1).variant_name(), "u64");
119    /// assert_eq!(Value::Str("x").variant_name(), "str");
120    /// ```
121    #[must_use]
122    pub const fn variant_name(&self) -> &'static str {
123        match self {
124            Self::Null => "null",
125            Self::Bool(_) => "bool",
126            Self::I64(_) => "i64",
127            Self::U64(_) => "u64",
128            Self::I128(_) => "i128",
129            Self::U128(_) => "u128",
130            Self::F64(_) => "f64",
131            Self::Str(_) => "str",
132            Self::Char(_) => "char",
133            Self::Bytes(_) => "bytes",
134            Self::Display(_) => "display",
135            Self::Debug(_) => "debug",
136            Self::Error(_) => "error",
137        }
138    }
139}
140
141/// Write `err` followed by `": <source>"` for each error in its chain.
142pub(crate) fn write_error_chain<W: fmt::Write + ?Sized>(
143    w: &mut W,
144    err: &(dyn core::error::Error + 'static),
145) -> fmt::Result {
146    write!(w, "{err}")?;
147    let mut source = err.source();
148    while let Some(s) = source {
149        write!(w, ": {s}")?;
150        source = s.source();
151    }
152    Ok(())
153}
154
155/// Write `bytes` as lowercase hexadecimal.
156pub(crate) fn write_hex<W: fmt::Write + ?Sized>(w: &mut W, bytes: &[u8]) -> fmt::Result {
157    const DIGITS: &[u8; 16] = b"0123456789abcdef";
158    for &b in bytes {
159        w.write_char(char::from(DIGITS[usize::from(b >> 4)]))?;
160        w.write_char(char::from(DIGITS[usize::from(b & 0x0f)]))?;
161    }
162    Ok(())
163}
164
165/// Write a float the way every built-in format renders it: `{:?}` for
166/// finite values (always round-trippable, always has `.` or `e`), and
167/// `NaN` / `inf` / `-inf` otherwise.
168pub(crate) fn write_f64<W: fmt::Write + ?Sized>(w: &mut W, n: f64) -> fmt::Result {
169    if n.is_finite() {
170        write!(w, "{n:?}")
171    } else if n.is_nan() {
172        w.write_str("NaN")
173    } else if n.is_sign_positive() {
174        w.write_str("inf")
175    } else {
176        w.write_str("-inf")
177    }
178}
179
180impl fmt::Display for Value<'_> {
181    /// Renders the value's plain text, without any format-specific
182    /// quoting or escaping. Floats use the same text as the built-in
183    /// formats.
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        match *self {
186            Self::Null => f.write_str("null"),
187            Self::Bool(b) => fmt::Display::fmt(&b, f),
188            Self::I64(n) => fmt::Display::fmt(&n, f),
189            Self::U64(n) => fmt::Display::fmt(&n, f),
190            Self::I128(n) => fmt::Display::fmt(&n, f),
191            Self::U128(n) => fmt::Display::fmt(&n, f),
192            Self::F64(n) => write_f64(f, n),
193            Self::Str(s) => f.write_str(s),
194            Self::Char(c) => fmt::Display::fmt(&c, f),
195            Self::Bytes(b) => write_hex(f, b),
196            Self::Display(d) => fmt::Display::fmt(d, f),
197            Self::Debug(d) => fmt::Debug::fmt(d, f),
198            Self::Error(e) => write_error_chain(f, e),
199        }
200    }
201}
202
203impl fmt::Debug for Value<'_> {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        match *self {
206            Self::Null => f.write_str("Null"),
207            Self::Bool(b) => f.debug_tuple("Bool").field(&b).finish(),
208            Self::I64(n) => f.debug_tuple("I64").field(&n).finish(),
209            Self::U64(n) => f.debug_tuple("U64").field(&n).finish(),
210            Self::I128(n) => f.debug_tuple("I128").field(&n).finish(),
211            Self::U128(n) => f.debug_tuple("U128").field(&n).finish(),
212            Self::F64(n) => f.debug_tuple("F64").field(&n).finish(),
213            Self::Str(s) => f.debug_tuple("Str").field(&s).finish(),
214            Self::Char(c) => f.debug_tuple("Char").field(&c).finish(),
215            Self::Bytes(b) => f.debug_tuple("Bytes").field(&b).finish(),
216            Self::Display(d) => f
217                .debug_tuple("Display")
218                .field(&format_args!("{d}"))
219                .finish(),
220            Self::Debug(d) => f.debug_tuple("Debug").field(&d).finish(),
221            Self::Error(e) => f.debug_tuple("Error").field(&format_args!("{e}")).finish(),
222        }
223    }
224}
225
226impl PartialEq for Value<'_> {
227    /// Structural equality for data variants. The lazy variants
228    /// ([`Value::Display`], [`Value::Debug`], [`Value::Error`]) compare
229    /// equal only when they point at the same object; their rendered
230    /// text is never computed for comparison. Floats follow IEEE
231    /// semantics (`NaN != NaN`).
232    fn eq(&self, other: &Self) -> bool {
233        match (*self, *other) {
234            (Self::Null, Self::Null) => true,
235            (Self::Bool(a), Self::Bool(b)) => a == b,
236            (Self::I64(a), Self::I64(b)) => a == b,
237            (Self::U64(a), Self::U64(b)) => a == b,
238            (Self::I128(a), Self::I128(b)) => a == b,
239            (Self::U128(a), Self::U128(b)) => a == b,
240            (Self::F64(a), Self::F64(b)) => a == b,
241            (Self::Str(a), Self::Str(b)) => a == b,
242            (Self::Char(a), Self::Char(b)) => a == b,
243            (Self::Bytes(a), Self::Bytes(b)) => a == b,
244            (Self::Display(a), Self::Display(b)) => core::ptr::addr_eq(a, b),
245            (Self::Debug(a), Self::Debug(b)) => core::ptr::addr_eq(a, b),
246            (Self::Error(a), Self::Error(b)) => core::ptr::addr_eq(a, b),
247            _ => false,
248        }
249    }
250}
251
252// Ergonomic conversions for call sites that don't want to spell out
253// the variant.
254
255impl<'a> From<&'a str> for Value<'a> {
256    fn from(value: &'a str) -> Self {
257        Self::Str(value)
258    }
259}
260
261impl<'a> From<&'a [u8]> for Value<'a> {
262    fn from(value: &'a [u8]) -> Self {
263        Self::Bytes(value)
264    }
265}
266
267impl From<bool> for Value<'_> {
268    fn from(value: bool) -> Self {
269        Self::Bool(value)
270    }
271}
272
273impl From<char> for Value<'_> {
274    fn from(value: char) -> Self {
275        Self::Char(value)
276    }
277}
278
279macro_rules! from_lossless_int {
280    ($($ty:ty => $variant:ident),* $(,)?) => {
281        $(
282            impl From<$ty> for Value<'_> {
283                fn from(value: $ty) -> Self {
284                    Self::$variant(value.into())
285                }
286            }
287        )*
288    };
289}
290
291from_lossless_int!(
292    i8 => I64, i16 => I64, i32 => I64, i64 => I64,
293    u8 => U64, u16 => U64, u32 => U64, u64 => U64,
294    i128 => I128, u128 => U128,
295);
296
297impl From<isize> for Value<'_> {
298    fn from(value: isize) -> Self {
299        // `isize` is at most 64 bits on every supported target; the
300        // 128-bit fallback keeps the conversion lossless regardless.
301        i64::try_from(value).map_or(Self::I128(value as i128), Self::I64)
302    }
303}
304
305impl From<usize> for Value<'_> {
306    fn from(value: usize) -> Self {
307        u64::try_from(value).map_or(Self::U128(value as u128), Self::U64)
308    }
309}
310
311impl From<f32> for Value<'_> {
312    fn from(value: f32) -> Self {
313        Self::F64(f64::from(value))
314    }
315}
316
317impl From<f64> for Value<'_> {
318    fn from(value: f64) -> Self {
319        Self::F64(value)
320    }
321}
322
323impl<'a, T> From<Option<T>> for Value<'a>
324where
325    T: Into<Value<'a>>,
326{
327    fn from(value: Option<T>) -> Self {
328        match value {
329            Some(v) => v.into(),
330            None => Self::Null,
331        }
332    }
333}
334
335#[cfg(feature = "std")]
336impl<'a> From<&'a String> for Value<'a> {
337    fn from(value: &'a String) -> Self {
338        Self::Str(value.as_str())
339    }
340}
341
342#[cfg(feature = "std")]
343impl<'a> From<&'a std::borrow::Cow<'a, str>> for Value<'a> {
344    fn from(value: &'a std::borrow::Cow<'a, str>) -> Self {
345        Self::Str(value.as_ref())
346    }
347}
348
349#[cfg(feature = "std")]
350impl<'a> From<&'a Vec<u8>> for Value<'a> {
351    fn from(value: &'a Vec<u8>) -> Self {
352        Self::Bytes(value.as_slice())
353    }
354}
355
356/// Owned counterpart of [`Value`], used wherever a value must outlive
357/// the call that produced it: logger default fields, thread-local
358/// context, and captured context snapshots. Lazy variants are rendered
359/// to text at capture time.
360#[cfg(feature = "std")]
361#[derive(Debug, Clone, PartialEq)]
362pub(crate) enum OwnedValue {
363    Null,
364    Bool(bool),
365    I64(i64),
366    U64(u64),
367    I128(i128),
368    U128(u128),
369    F64(f64),
370    Str(Box<str>),
371    Char(char),
372    Bytes(Box<[u8]>),
373}
374
375#[cfg(feature = "std")]
376impl OwnedValue {
377    pub(crate) fn from_value(v: Value<'_>) -> Self {
378        match v {
379            Value::Null => Self::Null,
380            Value::Bool(b) => Self::Bool(b),
381            Value::I64(n) => Self::I64(n),
382            Value::U64(n) => Self::U64(n),
383            Value::I128(n) => Self::I128(n),
384            Value::U128(n) => Self::U128(n),
385            Value::F64(n) => Self::F64(n),
386            Value::Str(s) => Self::Str(s.into()),
387            Value::Char(c) => Self::Char(c),
388            Value::Bytes(b) => Self::Bytes(b.into()),
389            other @ (Value::Display(_) | Value::Debug(_) | Value::Error(_)) => {
390                Self::Str(other.to_string().into_boxed_str())
391            }
392        }
393    }
394
395    pub(crate) fn as_value(&self) -> Value<'_> {
396        match self {
397            Self::Null => Value::Null,
398            Self::Bool(b) => Value::Bool(*b),
399            Self::I64(n) => Value::I64(*n),
400            Self::U64(n) => Value::U64(*n),
401            Self::I128(n) => Value::I128(*n),
402            Self::U128(n) => Value::U128(*n),
403            Self::F64(n) => Value::F64(*n),
404            Self::Str(s) => Value::Str(s),
405            Self::Char(c) => Value::Char(*c),
406            Self::Bytes(b) => Value::Bytes(b),
407        }
408    }
409}
410
411/// Owned counterpart of [`crate::Field`].
412#[cfg(feature = "std")]
413#[derive(Debug, Clone, PartialEq)]
414pub(crate) struct OwnedField {
415    pub(crate) key: Box<str>,
416    pub(crate) value: OwnedValue,
417}
418
419#[cfg(feature = "std")]
420impl OwnedField {
421    pub(crate) fn new(key: &str, value: Value<'_>) -> Self {
422        Self {
423            key: key.into(),
424            value: OwnedValue::from_value(value),
425        }
426    }
427
428    pub(crate) fn as_field(&self) -> crate::Field<'_> {
429        crate::Field {
430            key: &self.key,
431            value: self.value.as_value(),
432        }
433    }
434}
435
436#[cfg(all(test, feature = "std"))]
437mod tests {
438    use super::*;
439
440    #[derive(Debug)]
441    struct Outer(Inner);
442    #[derive(Debug)]
443    struct Inner;
444    impl fmt::Display for Outer {
445        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446            f.write_str("outer failed")
447        }
448    }
449    impl fmt::Display for Inner {
450        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451            f.write_str("inner cause")
452        }
453    }
454    impl core::error::Error for Outer {
455        fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
456            Some(&self.0)
457        }
458    }
459    impl core::error::Error for Inner {}
460
461    #[test]
462    fn test_value_from_str_is_borrowed() {
463        let s = String::from("hello");
464        let v: Value<'_> = s.as_str().into();
465        assert_eq!(v, Value::Str("hello"));
466        let v: Value<'_> = (&s).into();
467        assert_eq!(v, Value::Str("hello"));
468        let c = std::borrow::Cow::Borrowed("cow");
469        assert_eq!(Value::from(&c), Value::Str("cow"));
470        let bytes = vec![1_u8, 2];
471        assert_eq!(Value::from(&bytes), Value::Bytes(&[1, 2]));
472    }
473
474    #[test]
475    fn test_value_integer_conversions_are_lossless() {
476        assert_eq!(Value::from(42_i32), Value::I64(42));
477        assert_eq!(Value::from(42_u32), Value::U64(42));
478        assert_eq!(Value::from(-1_i64), Value::I64(-1));
479        assert_eq!(Value::from(-1_isize), Value::I64(-1));
480        assert_eq!(Value::from(7_usize), Value::U64(7));
481        assert_eq!(Value::from(u128::MAX), Value::U128(u128::MAX));
482        assert_eq!(Value::from(i128::MIN), Value::I128(i128::MIN));
483        assert_eq!(Value::from(1.5_f32), Value::F64(1.5));
484    }
485
486    #[test]
487    fn test_value_option_none_is_null() {
488        let some: Value<'_> = Some(7_u32).into();
489        assert_eq!(some, Value::U64(7));
490        let none: Value<'_> = Option::<u32>::None.into();
491        assert!(none.is_null());
492    }
493
494    #[test]
495    fn test_value_variant_name_is_unique_per_variant() {
496        let e = Outer(Inner);
497        let names: Vec<&str> = [
498            Value::Null,
499            Value::Bool(true),
500            Value::I64(0),
501            Value::U64(0),
502            Value::I128(0),
503            Value::U128(0),
504            Value::F64(0.0),
505            Value::Str(""),
506            Value::Char('a'),
507            Value::Bytes(&[]),
508            Value::Display(&1),
509            Value::Debug(&1),
510            Value::Error(&e),
511        ]
512        .iter()
513        .map(Value::variant_name)
514        .collect();
515        let mut dedup = names.clone();
516        dedup.sort_unstable();
517        dedup.dedup();
518        assert_eq!(dedup.len(), names.len(), "variant names must be unique");
519    }
520
521    #[test]
522    fn test_value_display_renders_each_variant() {
523        let e = Outer(Inner);
524        assert_eq!(Value::Null.to_string(), "null");
525        assert_eq!(Value::Bool(true).to_string(), "true");
526        assert_eq!(Value::I64(-5).to_string(), "-5");
527        assert_eq!(Value::U64(5).to_string(), "5");
528        assert_eq!(Value::I128(-5).to_string(), "-5");
529        assert_eq!(Value::U128(5).to_string(), "5");
530        assert_eq!(Value::F64(1.0).to_string(), "1.0");
531        assert_eq!(Value::F64(f64::NAN).to_string(), "NaN");
532        assert_eq!(Value::F64(f64::INFINITY).to_string(), "inf");
533        assert_eq!(Value::F64(f64::NEG_INFINITY).to_string(), "-inf");
534        assert_eq!(Value::Str("x").to_string(), "x");
535        assert_eq!(Value::Char('y').to_string(), "y");
536        assert_eq!(Value::Bytes(&[0x00, 0xab, 0xff]).to_string(), "00abff");
537        assert_eq!(Value::Display(&42).to_string(), "42");
538        assert_eq!(Value::Debug(&"q").to_string(), "\"q\"");
539        assert_eq!(Value::Error(&e).to_string(), "outer failed: inner cause");
540    }
541
542    #[test]
543    fn test_value_debug_is_readable() {
544        assert_eq!(format!("{:?}", Value::U64(3)), "U64(3)");
545        assert_eq!(format!("{:?}", Value::Display(&7)), "Display(7)");
546        assert_eq!(format!("{:?}", Value::Debug(&7)), "Debug(7)");
547        assert_eq!(format!("{:?}", Value::Null), "Null");
548        let e = Outer(Inner);
549        assert_eq!(format!("{:?}", Value::Error(&e)), "Error(outer failed)");
550    }
551
552    #[test]
553    fn test_value_eq_lazy_variants_compare_by_identity() {
554        let a = 5;
555        let b = 5;
556        assert_eq!(Value::Display(&a), Value::Display(&a));
557        assert_ne!(Value::Display(&a), Value::Display(&b));
558        assert_eq!(Value::Debug(&a), Value::Debug(&a));
559        assert_ne!(Value::F64(f64::NAN), Value::F64(f64::NAN));
560        assert_ne!(Value::U64(1), Value::I64(1));
561        let e = Outer(Inner);
562        assert_eq!(Value::Error(&e), Value::Error(&e));
563    }
564
565    #[test]
566    fn test_owned_value_renders_lazy_variants_to_text() {
567        let e = Outer(Inner);
568        let owned = OwnedValue::from_value(Value::Error(&e));
569        assert_eq!(owned.as_value(), Value::Str("outer failed: inner cause"));
570        let owned = OwnedValue::from_value(Value::Bytes(&[1, 2]));
571        assert_eq!(owned.as_value(), Value::Bytes(&[1, 2]));
572        for v in [
573            Value::Null,
574            Value::Bool(true),
575            Value::I64(-1),
576            Value::U64(1),
577            Value::I128(-2),
578            Value::U128(2),
579            Value::F64(0.5),
580            Value::Str("s"),
581            Value::Char('c'),
582        ] {
583            assert_eq!(OwnedValue::from_value(v).as_value(), v);
584        }
585    }
586
587    #[test]
588    fn test_float_text_is_round_trippable() {
589        for x in [1.0, 0.1, 1e300, f64::MAX, 5e-324, -0.0, 1e21, 1e-7] {
590            let s = Value::F64(x).to_string();
591            assert!(s.contains('.') || s.contains('e'), "{s}");
592            assert_eq!(s.parse::<f64>().unwrap().to_bits(), x.to_bits(), "{s}");
593        }
594    }
595}