1use opentelemetry::{
2 baggage::{BaggageExt, KeyValueMetadata},
3 otel_warn,
4 propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
5 Context,
6};
7use percent_encoding::{percent_decode_str, utf8_percent_encode, AsciiSet, CONTROLS};
8use std::iter;
9use std::sync::OnceLock;
10
11static BAGGAGE_HEADER: &str = "baggage";
12const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b';').add(b',').add(b'=');
13
14static BAGGAGE_FIELDS: OnceLock<[String; 1]> = OnceLock::new();
16#[inline]
17fn baggage_fields() -> &'static [String; 1] {
18 BAGGAGE_FIELDS.get_or_init(|| [BAGGAGE_HEADER.to_owned()])
19}
20
21#[derive(Debug, Default)]
67pub struct BaggagePropagator {
68 _private: (),
69}
70
71impl BaggagePropagator {
72 pub fn new() -> Self {
74 BaggagePropagator { _private: () }
75 }
76}
77
78impl TextMapPropagator for BaggagePropagator {
79 fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
81 let baggage = cx.baggage();
82 if !baggage.is_empty() {
83 let header_value = baggage
84 .iter()
85 .map(|(name, (value, metadata))| {
86 let metadata_str = metadata.as_str().trim();
87 let metadata_prefix = if metadata_str.is_empty() { "" } else { ";" };
88 utf8_percent_encode(name.as_str().trim(), FRAGMENT)
89 .chain(iter::once("="))
90 .chain(utf8_percent_encode(value.as_str().trim(), FRAGMENT))
91 .chain(iter::once(metadata_prefix))
92 .chain(iter::once(metadata_str))
93 .collect()
94 })
95 .collect::<Vec<String>>()
96 .join(",");
97 injector.set(BAGGAGE_HEADER, header_value);
98 }
99 }
100
101 fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
103 if let Some(header_value) = extractor.get(BAGGAGE_HEADER) {
104 let baggage = header_value.split(',').filter_map(|context_value| {
105 if let Some((name_and_value, props)) = context_value
106 .split(';')
107 .collect::<Vec<&str>>()
108 .split_first()
109 {
110 let mut iter = name_and_value.split('=');
111 if let (Some(name), Some(value)) = (iter.next(), iter.next()) {
112 let decode_name = percent_decode_str(name).decode_utf8();
113 let decode_value = percent_decode_str(value).decode_utf8();
114
115 if let (Ok(name), Ok(value)) = (decode_name, decode_value) {
116 let decoded_props = props
119 .iter()
120 .flat_map(|prop| percent_decode_str(prop).decode_utf8())
121 .map(|prop| prop.trim().to_string())
122 .collect::<Vec<String>>()
123 .join(";"); Some(KeyValueMetadata::new(
126 name.trim().to_owned(),
127 value.trim().to_string(),
128 decoded_props.as_str(),
129 ))
130 } else {
131 otel_warn!(
132 name: "BaggagePropagator.Extract.InvalidUTF8",
133 message = "Invalid UTF8 string in key values",
134 baggage_header = header_value,
135 );
136 None
137 }
138 } else {
139 otel_warn!(
140 name: "BaggagePropagator.Extract.InvalidKeyValueFormat",
141 message = "Invalid baggage key-value format",
142 baggage_header = header_value,
143 );
144 None
145 }
146 } else {
147 otel_warn!(
148 name: "BaggagePropagator.Extract.InvalidFormat",
149 message = "Invalid baggage format",
150 baggage_header = header_value);
151 None
152 }
153 });
154 cx.with_baggage(baggage)
155 } else {
156 cx.clone()
157 }
158 }
159
160 fn fields(&self) -> FieldIter<'_> {
161 FieldIter::new(baggage_fields())
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use opentelemetry::{baggage::BaggageMetadata, Key, KeyValue, StringValue, Value};
169 use std::collections::HashMap;
170
171 #[rustfmt::skip]
172 fn valid_extract_data() -> Vec<(&'static str, HashMap<Key, StringValue>)> {
173 vec![
174 ("key1=val1,key2=val2", vec![(Key::new("key1"), StringValue::from("val1")), (Key::new("key2"), StringValue::from("val2"))].into_iter().collect()),
176 ("key1 = val1, key2 =val2 ", vec![(Key::new("key1"), StringValue::from("val1")), (Key::new("key2"), StringValue::from("val2"))].into_iter().collect()),
178 ("key1=val1,key2=val2%2Cval3", vec![(Key::new("key1"), StringValue::from("val1")), (Key::new("key2"), StringValue::from("val2,val3"))].into_iter().collect()),
180 ("key1=val1,key2=val2,a,val3", vec![(Key::new("key1"), StringValue::from("val1")), (Key::new("key2"), StringValue::from("val2"))].into_iter().collect()),
182 ("key1=,key2=val2", vec![(Key::new("key1"), StringValue::from("")), (Key::new("key2"), StringValue::from("val2"))].into_iter().collect()),
184 ]
185 }
186
187 #[rustfmt::skip]
188 #[allow(clippy::type_complexity)]
189 fn valid_extract_data_with_metadata() -> Vec<(&'static str, HashMap<Key, (StringValue, BaggageMetadata)>)> {
190 vec![
191 ("key1=val1,key2=val2;prop=1", vec![(Key::new("key1"), (StringValue::from("val1"), BaggageMetadata::default())), (Key::new("key2"), (StringValue::from("val2"), BaggageMetadata::from("prop=1")))].into_iter().collect()),
193 ("key1=val1,key2=val2;prop1", vec![(Key::new("key1"), (StringValue::from("val1"), BaggageMetadata::default())), (Key::new("key2"), (StringValue::from("val2"), BaggageMetadata::from("prop1")))].into_iter().collect()),
195 ("key1=value1;property1;property2, key2 = value2, key3=value3; propertyKey=propertyValue",
196 vec![
197 (Key::new("key1"), (StringValue::from("value1"), BaggageMetadata::from("property1;property2"))),
198 (Key::new("key2"), (StringValue::from("value2"), BaggageMetadata::default())),
199 (Key::new("key3"), (StringValue::from("value3"), BaggageMetadata::from("propertyKey=propertyValue"))),
200 ].into_iter().collect()),
201 ]
202 }
203
204 #[rustfmt::skip]
205 fn valid_inject_data() -> Vec<(Vec<KeyValue>, Vec<&'static str>)> {
206 vec![
207 (vec![KeyValue::new("key1", "val1"), KeyValue::new("key2", "val2")], vec!["key1=val1", "key2=val2"]),
209 (vec![KeyValue::new("key1", "val1,val2"), KeyValue::new("key2", "val3=4")], vec!["key1=val1%2Cval2", "key2=val3%3D4"]),
211 (
213 vec![
214 KeyValue::new("key1", true),
215 KeyValue::new("key2", Value::I64(123)),
216 KeyValue::new("key3", Value::F64(123.567)),
217 ],
218 vec![
219 "key1=true",
220 "key2=123",
221 "key3=123.567",
222 ],
223 ),
224 (
226 vec![
227 KeyValue::new("key1", Value::Array(vec![true, false].into())),
228 KeyValue::new("key2", Value::Array(vec![123, 456].into())),
229 KeyValue::new("key3", Value::Array(vec![StringValue::from("val1"), StringValue::from("val2")].into())),
230 ],
231 vec![
232 "key1=[true%2Cfalse]",
233 "key2=[123%2C456]",
234 "key3=[%22val1%22%2C%22val2%22]",
235 ],
236 ),
237 ]
238 }
239
240 #[rustfmt::skip]
241 fn valid_inject_data_metadata() -> Vec<(Vec<KeyValueMetadata>, Vec<&'static str>)> {
242 vec![
243 (
244 vec![
245 KeyValueMetadata::new("key1", "val1", "prop1"),
246 KeyValue::new("key2", "val2").into(),
247 KeyValueMetadata::new("key3", "val3", "anykey=anyvalue"),
248 ],
249 vec![
250 "key1=val1;prop1",
251 "key2=val2",
252 "key3=val3;anykey=anyvalue",
253 ],
254 )
255 ]
256 }
257
258 #[test]
259 fn extract_baggage() {
260 let propagator = BaggagePropagator::new();
261
262 for (header_value, kvs) in valid_extract_data() {
263 let mut extractor: HashMap<String, String> = HashMap::new();
264 extractor.insert(BAGGAGE_HEADER.to_string(), header_value.to_string());
265 let context = propagator.extract(&extractor);
266 let baggage = context.baggage();
267
268 assert_eq!(kvs.len(), baggage.len());
269 for (key, (value, _metadata)) in baggage {
270 assert_eq!(Some(value), kvs.get(key))
271 }
272 }
273 }
274
275 #[test]
276 fn inject_baggage() {
277 let propagator = BaggagePropagator::new();
278
279 for (kvm, header_parts) in valid_inject_data() {
280 let mut injector = HashMap::new();
281 let cx = Context::current_with_baggage(kvm);
282 propagator.inject_context(&cx, &mut injector);
283 let header_value = injector.get(BAGGAGE_HEADER).unwrap();
284 assert_eq!(header_parts.join(",").len(), header_value.len(),);
285 for header_part in &header_parts {
286 assert!(header_value.contains(header_part),)
287 }
288 }
289 }
290
291 #[test]
292 fn extract_baggage_with_metadata() {
293 let propagator = BaggagePropagator::new();
294 for (header_value, kvm) in valid_extract_data_with_metadata() {
295 let mut extractor: HashMap<String, String> = HashMap::new();
296 extractor.insert(BAGGAGE_HEADER.to_string(), header_value.to_string());
297 let context = propagator.extract(&extractor);
298 let baggage = context.baggage();
299
300 assert_eq!(kvm.len(), baggage.len());
301 for (key, value_and_prop) in baggage {
302 assert_eq!(Some(value_and_prop), kvm.get(key))
303 }
304 }
305 }
306
307 #[test]
308 fn inject_baggage_with_metadata() {
309 let propagator = BaggagePropagator::new();
310
311 for (kvm, header_parts) in valid_inject_data_metadata() {
312 let mut injector = HashMap::new();
313 let cx = Context::current_with_baggage(kvm);
314 propagator.inject_context(&cx, &mut injector);
315 let header_value = injector.get(BAGGAGE_HEADER).unwrap();
316
317 assert_eq!(header_parts.join(",").len(), header_value.len());
318 for header_part in &header_parts {
319 assert!(header_value.contains(header_part),)
320 }
321 }
322 }
323}