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