blob: 8c78c4b203b818f3a92c89396a72234837f79943 [file] [log] [blame]
//! A `MakeVisitor` wrapper that separates formatted fields with a delimiter.
use super::{MakeVisitor, VisitFmt, VisitOutput};
use core::fmt;
use tracing_core::field::{Field, Visit};
/// A `MakeVisitor` wrapper that wraps a visitor that writes formatted output so
/// that a delimiter is inserted between writing formatted field values.
#[derive(Debug, Clone)]
pub struct Delimited<D, V> {
delimiter: D,
inner: V,
}
/// A visitor wrapper that inserts a delimiter after the wrapped visitor formats
/// a field value.
#[derive(Debug)]
pub struct VisitDelimited<D, V> {
delimiter: D,
seen: bool,
inner: V,
err: fmt::Result,
}
// === impl Delimited ===
impl<D, V, T> MakeVisitor<T> for Delimited<D, V>
where
D: AsRef<str> + Clone,
V: MakeVisitor<T>,
V::Visitor: VisitFmt,
{
type Visitor = VisitDelimited<D, V::Visitor>;
fn make_visitor(&self, target: T) -> Self::Visitor {
let inner = self.inner.make_visitor(target);
VisitDelimited::new(self.delimiter.clone(), inner)
}
}
impl<D, V> Delimited<D, V> {
/// Returns a new [`MakeVisitor`] implementation that wraps `inner` so that
/// it will format each visited field separated by the provided `delimiter`.
///
/// [`MakeVisitor`]: ../trait.MakeVisitor.html
pub fn new(delimiter: D, inner: V) -> Self {
Self { delimiter, inner }
}
}
// === impl VisitDelimited ===
impl<D, V> VisitDelimited<D, V> {
/// Returns a new [`Visit`] implementation that wraps `inner` so that
/// each formatted field is separated by the provided `delimiter`.
///
/// [`Visit`]: https://docs.rs/tracing-core/0.1.6/tracing_core/field/trait.Visit.html
pub fn new(delimiter: D, inner: V) -> Self {
Self {
delimiter,
inner,
seen: false,
err: Ok(()),
}
}
fn delimit(&mut self)
where
V: VisitFmt,
D: AsRef<str>,
{
if self.err.is_err() {
return;
}
if self.seen {
self.err = self.inner.writer().write_str(self.delimiter.as_ref());
}
self.seen = true;
}
}
impl<D, V> Visit for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
fn record_i64(&mut self, field: &Field, value: i64) {
self.delimit();
self.inner.record_i64(field, value);
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.delimit();
self.inner.record_u64(field, value);
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.delimit();
self.inner.record_bool(field, value);
}
fn record_str(&mut self, field: &Field, value: &str) {
self.delimit();
self.inner.record_str(field, value);
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.delimit();
self.inner.record_debug(field, value);
}
}
impl<D, V> VisitOutput<fmt::Result> for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
fn finish(self) -> fmt::Result {
self.err?;
self.inner.finish()
}
}
impl<D, V> VisitFmt for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
fn writer(&mut self) -> &mut dyn fmt::Write {
self.inner.writer()
}
}
#[cfg(test)]
#[cfg(all(test, feature = "alloc"))]
mod test {
use super::*;
use crate::field::test_util::*;
#[test]
fn delimited_visitor() {
let mut s = String::new();
let visitor = DebugVisitor::new(&mut s);
let mut visitor = VisitDelimited::new(", ", visitor);
TestAttrs1::with(|attrs| attrs.record(&mut visitor));
visitor.finish().unwrap();
assert_eq!(
s.as_str(),
"question=\"life, the universe, and everything\", tricky=true, can_you_do_it=true"
);
}
#[test]
fn delimited_new_visitor() {
let make = Delimited::new("; ", MakeDebug);
TestAttrs1::with(|attrs| {
let mut s = String::new();
{
let mut v = make.make_visitor(&mut s);
attrs.record(&mut v);
}
assert_eq!(
s.as_str(),
"question=\"life, the universe, and everything\"; tricky=true; can_you_do_it=true"
);
});
TestAttrs2::with(|attrs| {
let mut s = String::new();
{
let mut v = make.make_visitor(&mut s);
attrs.record(&mut v);
}
assert_eq!(
s.as_str(),
"question=None; question.answer=42; tricky=true; can_you_do_it=false"
);
});
}
}