use std::time::{Duration, Instant};
use tonic::Request;
const TRACEPARENT: &str = "traceparent";
const TRACESTATE: &str = "tracestate";
pub fn inject_traceparent<T>(req: &mut Request<T>, traceparent: &str) -> bool {
match traceparent.parse() {
Ok(v) => {
req.metadata_mut().insert(TRACEPARENT, v);
true
}
Err(_) => {
tracing::warn!(traceparent, "malformed traceparent — not injected");
false
}
}
}
pub fn inject_tracestate<T>(req: &mut Request<T>, tracestate: &str) -> bool {
match tracestate.parse() {
Ok(v) => {
req.metadata_mut().insert(TRACESTATE, v);
true
}
Err(_) => false,
}
}
pub fn inject_deadline<T>(req: &mut Request<T>, deadline: Instant) -> bool {
let now = Instant::now();
if deadline <= now {
return false;
}
let remaining = deadline - now;
let header = format_grpc_timeout(remaining);
if let Ok(v) = header.parse() {
req.metadata_mut().insert("grpc-timeout", v);
true
} else {
false
}
}
pub(crate) fn format_grpc_timeout(d: Duration) -> String {
let nanos = d.as_nanos();
if nanos == 0 {
return "0n".into();
}
let hours = nanos / 3_600_000_000_000;
if hours > 0 && nanos.is_multiple_of(3_600_000_000_000) {
return format!("{hours}H");
}
let mins = nanos / 60_000_000_000;
if mins > 0 && nanos.is_multiple_of(60_000_000_000) {
return format!("{mins}M");
}
let secs = nanos / 1_000_000_000;
if secs > 0 && nanos.is_multiple_of(1_000_000_000) {
return format!("{secs}S");
}
let millis = nanos / 1_000_000;
if millis > 0 && nanos.is_multiple_of(1_000_000) {
return format!("{millis}m");
}
let micros = nanos / 1_000;
if micros > 0 && nanos.is_multiple_of(1_000) {
return format!("{micros}u");
}
format!("{nanos}n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inject_traceparent_writes_header() {
let mut req = Request::new(());
let ok = inject_traceparent(
&mut req,
"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
);
assert!(ok);
let v = req.metadata().get("traceparent").unwrap();
assert!(v.to_str().unwrap().starts_with("00-"));
}
#[test]
fn inject_malformed_traceparent_is_noop() {
let mut req = Request::new(());
let ok = inject_traceparent(&mut req, "bad\nvalue");
assert!(!ok);
assert!(req.metadata().get("traceparent").is_none());
}
#[test]
fn format_grpc_timeout_picks_coarsest_lossless_unit() {
assert_eq!(format_grpc_timeout(Duration::from_secs(3600)), "1H");
assert_eq!(format_grpc_timeout(Duration::from_secs(120)), "2M");
assert_eq!(format_grpc_timeout(Duration::from_secs(5)), "5S");
assert_eq!(format_grpc_timeout(Duration::from_millis(450)), "450m");
assert_eq!(format_grpc_timeout(Duration::from_micros(750)), "750u");
assert_eq!(format_grpc_timeout(Duration::from_nanos(123)), "123n");
}
#[test]
fn format_grpc_timeout_sub_unit_boundary_uses_finer_unit() {
assert_eq!(format_grpc_timeout(Duration::from_millis(1500)), "1500m");
}
#[test]
fn inject_deadline_future_writes_grpc_timeout() {
let mut req = Request::new(());
let deadline = Instant::now() + Duration::from_secs(5);
assert!(inject_deadline(&mut req, deadline));
let header = req.metadata().get("grpc-timeout").unwrap();
let s = header.to_str().unwrap();
assert!(s.ends_with(['H', 'M', 'S', 'm', 'u', 'n']));
}
#[test]
fn inject_deadline_past_returns_false() {
let mut req = Request::new(());
let past = Instant::now() - Duration::from_secs(1);
assert!(!inject_deadline(&mut req, past));
assert!(req.metadata().get("grpc-timeout").is_none());
}
}