use serde::Deserialize;
pub const IPC_CONTEXT_ARG: &str = "auditaurTraceContext";
pub const IPC_TRACEPARENT_HEADER: &str = "traceparent";
#[derive(Debug, Clone, Deserialize)]
pub struct IpcTraceContext {
traceparent: Option<String>,
}
impl IpcTraceContext {
pub fn traceparent(&self) -> Option<&str> {
self.traceparent
.as_deref()
.filter(|value| is_w3c_traceparent(value))
}
}
pub fn ipc_traceparent(context: Option<&IpcTraceContext>) -> &str {
context
.and_then(IpcTraceContext::traceparent)
.unwrap_or_default()
}
pub fn ipc_traceparent_from_request<'a>(request: &'a tauri::ipc::Request<'a>) -> &'a str {
ipc_traceparent_from_headers(request.headers())
}
pub fn ipc_traceparent_from_request_or_context<'a>(
request: &'a tauri::ipc::Request<'a>,
context: Option<&'a IpcTraceContext>,
) -> &'a str {
let header_traceparent = ipc_traceparent_from_request(request);
if header_traceparent.is_empty() {
ipc_traceparent(context)
} else {
header_traceparent
}
}
pub fn ipc_traceparent_from_headers(headers: &tauri::http::HeaderMap) -> &str {
headers
.get(IPC_TRACEPARENT_HEADER)
.and_then(|value| value.to_str().ok())
.filter(|value| is_w3c_traceparent(value))
.unwrap_or_default()
}
fn is_w3c_traceparent(value: &str) -> bool {
let mut parts = value.split('-');
let version = parts.next();
let trace_id = parts.next();
let parent_span_id = parts.next();
let flags = parts.next();
parts.next().is_none()
&& version.is_some_and(|value| is_hex_len(value, 2))
&& trace_id.is_some_and(|value| is_hex_len(value, 32))
&& parent_span_id.is_some_and(|value| is_hex_len(value, 16))
&& flags.is_some_and(|value| is_hex_len(value, 2))
}
fn is_hex_len(value: &str, len: usize) -> bool {
value.len() == len && value.bytes().all(|byte| byte.is_ascii_hexdigit())
}
#[cfg(test)]
mod tests {
use super::{ipc_traceparent, ipc_traceparent_from_headers, IpcTraceContext};
use serde_json::json;
#[test]
fn accepts_valid_traceparent() {
let context = IpcTraceContext {
traceparent: Some(
"00-00112233445566778899aabbccddeeff-0123456789abcdef-01".to_string(),
),
};
assert_eq!(
ipc_traceparent(Some(&context)),
"00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
);
}
#[test]
fn ignores_invalid_traceparent() {
let context = IpcTraceContext {
traceparent: Some("not-a-traceparent".to_string()),
};
assert_eq!(ipc_traceparent(Some(&context)), "");
assert_eq!(ipc_traceparent(None), "");
}
#[test]
fn deserializes_missing_or_extra_fields_safely() {
let missing: IpcTraceContext = serde_json::from_value(json!({})).unwrap();
let extra: IpcTraceContext = serde_json::from_value(json!({
"traceparent": "00-00112233445566778899aabbccddeeff-0123456789abcdef-01",
"future": true
}))
.unwrap();
assert_eq!(ipc_traceparent(Some(&missing)), "");
assert_eq!(
ipc_traceparent(Some(&extra)),
"00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
);
}
#[test]
fn extracts_valid_traceparent_from_ipc_headers() {
let mut headers = tauri::http::HeaderMap::new();
headers.insert(
"traceparent",
"00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
.parse()
.unwrap(),
);
assert_eq!(
ipc_traceparent_from_headers(&headers),
"00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
);
}
#[test]
fn ignores_invalid_traceparent_header() {
let mut headers = tauri::http::HeaderMap::new();
headers.insert("traceparent", "not-a-traceparent".parse().unwrap());
assert_eq!(ipc_traceparent_from_headers(&headers), "");
}
}