tauri_plugin_auditaur/
ipc.rs1use serde::Deserialize;
2
3pub const IPC_CONTEXT_ARG: &str = "auditaurTraceContext";
5pub const IPC_TRACEPARENT_HEADER: &str = "traceparent";
7
8#[derive(Debug, Clone, Deserialize)]
13pub struct IpcTraceContext {
14 traceparent: Option<String>,
15}
16
17impl IpcTraceContext {
18 pub fn traceparent(&self) -> Option<&str> {
20 self.traceparent
21 .as_deref()
22 .filter(|value| is_w3c_traceparent(value))
23 }
24}
25
26pub fn ipc_traceparent(context: Option<&IpcTraceContext>) -> &str {
28 context
29 .and_then(IpcTraceContext::traceparent)
30 .unwrap_or_default()
31}
32
33pub fn ipc_traceparent_from_request<'a>(request: &'a tauri::ipc::Request<'a>) -> &'a str {
35 ipc_traceparent_from_headers(request.headers())
36}
37
38pub 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
52pub 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}