use std::ops::Range;
use super::collect::{message_fields, raw_line_fields};
use super::kind::{Field, FieldLocation, RenderError, RenderedEntry};
pub fn apply_fields<'a>(
text: &str,
fields: impl IntoIterator<Item = &'a Field>,
f: impl Fn(&Field, &str) -> Option<String>,
) -> Result<String, RenderError> {
let mut replacements: Vec<(FieldLocation, Range<usize>, String)> = Vec::new();
for field in fields {
let range = field.range.clone();
if range.end > text.len() {
return Err(RenderError::OutOfBounds {
at: field.at,
range,
len: text.len(),
});
}
if !text.is_char_boundary(range.start) || !text.is_char_boundary(range.end) {
return Err(RenderError::NotOnCharBoundary {
at: field.at,
range,
});
}
if let Some(new) = f(field, &text[range.clone()]) {
replacements.push((field.at, range, new));
}
}
replacements.sort_by(|(_, a, _), (_, b, _)| {
(a.start, std::cmp::Reverse(a.end)).cmp(&(b.start, std::cmp::Reverse(b.end)))
});
let mut out = String::with_capacity(text.len());
let mut cursor = 0;
let mut accepted: Option<Range<usize>> = None;
for (at, range, new) in replacements {
if let Some(prev) = &accepted {
if range.start < prev.end {
if range.end <= prev.end {
continue;
}
return Err(RenderError::OverlappingSpans {
at,
first: prev.clone(),
second: range,
});
}
}
out.push_str(&text[cursor..range.start]);
out.push_str(&new);
cursor = range.end;
accepted = Some(range);
}
out.push_str(&text[cursor..]);
Ok(out)
}
impl crate::stream::LogEntry {
pub fn fields(&self) -> Vec<Field> {
let mut out = message_fields(&self.message);
for (i, line) in self.attached.iter().enumerate() {
out.extend(raw_line_fields(line, FieldLocation::Attached(i)));
}
out
}
pub fn render_with(
&self,
f: impl Fn(&Field, &str) -> Option<String>,
) -> Result<RenderedEntry, RenderError> {
let fields = self.fields();
let message = apply_fields(
&self.message,
fields.iter().filter(|x| x.at == FieldLocation::Message),
&f,
)?;
let attached = self
.attached
.iter()
.enumerate()
.map(|(i, line)| {
apply_fields(
line,
fields.iter().filter(|x| x.at == FieldLocation::Attached(i)),
&f,
)
})
.collect::<Result<Vec<_>, _>>()?;
Ok(RenderedEntry { message, attached })
}
}