use std::{borrow::Cow, time::Duration};
use bytes::BytesMut;
pub trait BuildableTo<Args> {
fn build(self) -> Args;
}
pub trait EnumCount {
const COUNT: usize;
}
#[derive(Debug, Default)]
pub struct FieldCapture {
strings: Vec<(&'static str, String)>,
u64s: Vec<(&'static str, u64)>,
i64s: Vec<(&'static str, i64)>,
f64s: Vec<(&'static str, f64)>,
bools: Vec<(&'static str, bool)>,
pub scratch: BytesMut,
}
impl FieldCapture {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.strings.clear();
self.u64s.clear();
self.i64s.clear();
self.f64s.clear();
self.bools.clear();
self.scratch.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.strings.len() + self.u64s.len() + self.i64s.len() + self.f64s.len() + self.bools.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn record_str(&mut self, name: &'static str, value: impl Into<String>) {
self.strings.push((name, value.into()));
}
pub fn record_u64(&mut self, name: &'static str, value: u64) {
self.u64s.push((name, value));
}
pub fn record_i64(&mut self, name: &'static str, value: i64) {
self.i64s.push((name, value));
}
pub fn record_f64(&mut self, name: &'static str, value: f64) {
self.f64s.push((name, value));
}
pub fn record_bool(&mut self, name: &'static str, value: bool) {
self.bools.push((name, value));
}
#[must_use]
pub fn string(&self, name: &str) -> Option<&str> {
self.strings
.iter()
.rev()
.find(|(k, _)| *k == name)
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn u64(&self, name: &str) -> Option<u64> {
self.u64s
.iter()
.rev()
.find(|(k, _)| *k == name)
.map(|(_, v)| *v)
}
#[must_use]
pub fn i64(&self, name: &str) -> Option<i64> {
self.i64s
.iter()
.rev()
.find(|(k, _)| *k == name)
.map(|(_, v)| *v)
}
#[must_use]
pub fn f64(&self, name: &str) -> Option<f64> {
self.f64s
.iter()
.rev()
.find(|(k, _)| *k == name)
.map(|(_, v)| *v)
}
#[must_use]
pub fn bool(&self, name: &str) -> Option<bool> {
self.bools
.iter()
.rev()
.find(|(k, _)| *k == name)
.map(|(_, v)| *v)
}
#[must_use]
pub fn duration(&self, name: &str) -> Option<Duration> {
self.u64(name).map(Duration::from_nanos)
}
pub fn iter_strings(&self) -> impl Iterator<Item = (&'static str, &str)> + '_ {
self.strings.iter().map(|(k, v)| (*k, v.as_str()))
}
}
#[derive(Debug, Clone, Copy)]
pub struct SpanFrame<'a> {
pub name: &'a str,
pub target: &'a str,
}
#[derive(Debug, Clone, Copy)]
pub struct SpanCtx<'a> {
pub labels: &'a [(&'static str, &'a str)],
pub spans: &'a [SpanFrame<'a>],
}
impl<'a> SpanCtx<'a> {
#[must_use]
pub const fn empty() -> Self {
Self {
labels: &[],
spans: &[],
}
}
#[must_use]
pub fn label(&self, name: &str) -> Option<&'a str> {
self.labels
.iter()
.rev()
.find_map(|(k, v)| if *k == name { Some(*v) } else { None })
}
#[must_use]
pub fn span_path(&self) -> Cow<'a, str> {
match self.spans {
[] => Cow::Borrowed(""),
[only] => Cow::Borrowed(only.name),
multi => {
let mut s = String::with_capacity(multi.iter().map(|f| f.name.len() + 1).sum());
for (i, f) in multi.iter().enumerate() {
if i > 0 {
s.push(':');
}
s.push_str(f.name);
}
Cow::Owned(s)
}
}
}
#[must_use]
pub fn target(&self) -> Option<&'a str> {
self.spans.last().map(|f| f.target)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_capture_should_record_and_lookup() {
let mut fc = FieldCapture::new();
fc.record_str("route", "list_users");
fc.record_u64("latency_ms", 42);
fc.record_bool("ok", true);
assert_eq!(fc.string("route"), Some("list_users"));
assert_eq!(fc.u64("latency_ms"), Some(42));
assert_eq!(fc.bool("ok"), Some(true));
assert_eq!(fc.len(), 3);
}
#[test]
fn test_field_capture_clear_should_preserve_capacity() {
let mut fc = FieldCapture::new();
for i in 0..32 {
fc.record_u64("x", i);
}
let cap_u64 = fc.u64s.capacity();
fc.clear();
assert!(fc.is_empty());
assert_eq!(fc.u64s.capacity(), cap_u64);
}
#[test]
fn test_span_ctx_label_should_find_innermost_match() {
let labels: &[(&'static str, &str)] = &[("tenant", "alpha"), ("tenant", "beta")];
let ctx = SpanCtx { labels, spans: &[] };
assert_eq!(ctx.label("tenant"), Some("beta"));
}
#[test]
fn test_span_ctx_span_path_should_join() {
let frames = [
SpanFrame {
name: "request",
target: "axum",
},
SpanFrame {
name: "auth",
target: "myapp::auth",
},
SpanFrame {
name: "db_query",
target: "sqlx",
},
];
let ctx = SpanCtx {
labels: &[],
spans: &frames,
};
assert_eq!(ctx.span_path(), Cow::Borrowed("request:auth:db_query"));
assert_eq!(ctx.target(), Some("sqlx"));
}
}