Skip to main content

tauri_plugin_auditaur/
ipc.rs

1use serde::Deserialize;
2
3/// Reserved Tauri invoke argument used by Auditaur's experimental IPC trace bridge.
4pub const IPC_CONTEXT_ARG: &str = "auditaurTraceContext";
5/// Reserved Tauri invoke request header used by Auditaur's IPC trace bridge.
6pub const IPC_TRACEPARENT_HEADER: &str = "traceparent";
7
8/// W3C trace context carried from Auditaur's frontend invoke wrapper.
9///
10/// Add this as an optional `auditaur_trace_context` argument on Tauri commands
11/// that should continue frontend invoke traces in backend `tracing` spans.
12#[derive(Debug, Clone, Deserialize)]
13pub struct IpcTraceContext {
14    traceparent: Option<String>,
15}
16
17impl IpcTraceContext {
18    /// Returns the valid W3C `traceparent` value, if one was provided.
19    pub fn traceparent(&self) -> Option<&str> {
20        self.traceparent
21            .as_deref()
22            .filter(|value| is_w3c_traceparent(value))
23    }
24}
25
26/// Extracts a `traceparent` field value for use in `#[tracing::instrument]`.
27pub fn ipc_traceparent(context: Option<&IpcTraceContext>) -> &str {
28    context
29        .and_then(IpcTraceContext::traceparent)
30        .unwrap_or_default()
31}
32
33/// Extracts a `traceparent` field value from a Tauri IPC request.
34pub fn ipc_traceparent_from_request<'a>(request: &'a tauri::ipc::Request<'a>) -> &'a str {
35    ipc_traceparent_from_headers(request.headers())
36}
37
38/// Extracts a `traceparent` field value from a Tauri IPC request, falling back to
39/// the legacy reserved invoke argument when request headers are unavailable.
40pub fn ipc_traceparent_from_request_or_context<'a>(
41    request: &'a tauri::ipc::Request<'a>,
42    context: Option<&'a IpcTraceContext>,
43) -> &'a str {
44    let header_traceparent = ipc_traceparent_from_request(request);
45    if header_traceparent.is_empty() {
46        ipc_traceparent(context)
47    } else {
48        header_traceparent
49    }
50}
51
52/// Extracts a `traceparent` field value from Tauri IPC request headers.
53pub fn ipc_traceparent_from_headers(headers: &tauri::http::HeaderMap) -> &str {
54    headers
55        .get(IPC_TRACEPARENT_HEADER)
56        .and_then(|value| value.to_str().ok())
57        .filter(|value| is_w3c_traceparent(value))
58        .unwrap_or_default()
59}
60
61fn is_w3c_traceparent(value: &str) -> bool {
62    let mut parts = value.split('-');
63    let version = parts.next();
64    let trace_id = parts.next();
65    let parent_span_id = parts.next();
66    let flags = parts.next();
67    parts.next().is_none()
68        && version.is_some_and(|value| is_hex_len(value, 2))
69        && trace_id.is_some_and(|value| is_hex_len(value, 32))
70        && parent_span_id.is_some_and(|value| is_hex_len(value, 16))
71        && flags.is_some_and(|value| is_hex_len(value, 2))
72}
73
74fn is_hex_len(value: &str, len: usize) -> bool {
75    value.len() == len && value.bytes().all(|byte| byte.is_ascii_hexdigit())
76}
77
78#[cfg(test)]
79mod tests {
80    use super::{ipc_traceparent, ipc_traceparent_from_headers, IpcTraceContext};
81    use serde_json::json;
82
83    #[test]
84    fn accepts_valid_traceparent() {
85        let context = IpcTraceContext {
86            traceparent: Some(
87                "00-00112233445566778899aabbccddeeff-0123456789abcdef-01".to_string(),
88            ),
89        };
90
91        assert_eq!(
92            ipc_traceparent(Some(&context)),
93            "00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
94        );
95    }
96
97    #[test]
98    fn ignores_invalid_traceparent() {
99        let context = IpcTraceContext {
100            traceparent: Some("not-a-traceparent".to_string()),
101        };
102
103        assert_eq!(ipc_traceparent(Some(&context)), "");
104        assert_eq!(ipc_traceparent(None), "");
105    }
106
107    #[test]
108    fn deserializes_missing_or_extra_fields_safely() {
109        let missing: IpcTraceContext = serde_json::from_value(json!({})).unwrap();
110        let extra: IpcTraceContext = serde_json::from_value(json!({
111            "traceparent": "00-00112233445566778899aabbccddeeff-0123456789abcdef-01",
112            "future": true
113        }))
114        .unwrap();
115
116        assert_eq!(ipc_traceparent(Some(&missing)), "");
117        assert_eq!(
118            ipc_traceparent(Some(&extra)),
119            "00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
120        );
121    }
122
123    #[test]
124    fn extracts_valid_traceparent_from_ipc_headers() {
125        let mut headers = tauri::http::HeaderMap::new();
126        headers.insert(
127            "traceparent",
128            "00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
129                .parse()
130                .unwrap(),
131        );
132
133        assert_eq!(
134            ipc_traceparent_from_headers(&headers),
135            "00-00112233445566778899aabbccddeeff-0123456789abcdef-01"
136        );
137    }
138
139    #[test]
140    fn ignores_invalid_traceparent_header() {
141        let mut headers = tauri::http::HeaderMap::new();
142        headers.insert("traceparent", "not-a-traceparent".parse().unwrap());
143
144        assert_eq!(ipc_traceparent_from_headers(&headers), "");
145    }
146}