From bf623d931b68e9fd726546af0d89fa396a4a56f3 Mon Sep 17 00:00:00 2001 From: William Perron Date: Thu, 24 Nov 2022 14:40:57 -0500 Subject: [PATCH] Implement Display on Baggage Fixes #905 --- opentelemetry-api/Cargo.toml | 1 + opentelemetry-api/src/baggage.rs | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/opentelemetry-api/Cargo.toml b/opentelemetry-api/Cargo.toml index 82cf6c0849e..183bce45e3e 100644 --- a/opentelemetry-api/Cargo.toml +++ b/opentelemetry-api/Cargo.toml @@ -18,6 +18,7 @@ once_cell = "1.12.0" pin-project-lite = { version = "0.2", optional = true } thiserror = "1" tokio-stream = { version = "0.1", optional = true } +urlencoding = "2.1.2" [package.metadata.docs.rs] all-features = true diff --git a/opentelemetry-api/src/baggage.rs b/opentelemetry-api/src/baggage.rs index 65f32615d7b..c90e4209db6 100644 --- a/opentelemetry-api/src/baggage.rs +++ b/opentelemetry-api/src/baggage.rs @@ -17,7 +17,10 @@ use crate::{Context, Key, KeyValue, Value}; use once_cell::sync::Lazy; use std::collections::{hash_map, HashMap}; +use std::fmt; use std::iter::FromIterator; +use urlencoding::encode; +// use urlencoding::encode; static DEFAULT_BAGGAGE: Lazy = Lazy::new(Baggage::default); @@ -280,6 +283,26 @@ impl FromIterator for Baggage { } } +impl fmt::Display for Baggage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut v: Vec = self + .into_iter() + .map(|(k, v)| { + let mut val = encode(&v.0.as_str()).to_string(); + if !v.1.as_str().is_empty() { + val.push_str(format!(";{}", v.1.as_str()).as_str()) + } + + format!("{}={}", k, val) + }) + .collect(); + + v.sort(); + let s = v.join(","); + Ok(write!(f, "{}", s)?) + } +} + /// Methods for sorting and retrieving baggage data in a context. pub trait BaggageExt { /// Returns a clone of the given context with the included name/value pairs. @@ -397,6 +420,12 @@ impl From<&str> for BaggageMetadata { } } +impl fmt::Display for BaggageMetadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(write!(f, "{}", self.as_str())?) + } +} + /// [`Baggage`] name/value pairs with their associated metadata. #[derive(Clone, Debug, PartialEq)] pub struct KeyValueMetadata { @@ -436,6 +465,8 @@ impl From for KeyValueMetadata { #[cfg(test)] mod tests { + use crate::StringValue; + use super::*; #[test] @@ -494,4 +525,49 @@ mod tests { let baggage = data.into_iter().collect::(); assert_eq!(baggage.len(), 3) } + + #[test] + fn serialize_baggage_as_string() { + // Empty baggage + let b = Baggage::default(); + assert_eq!("", b.to_string()); + + // "single member empty value no properties" + let mut b = Baggage::default(); + b.insert("foo", StringValue::from("")); + assert_eq!("foo=", b.to_string()); + + // "single member no properties" + let mut b = Baggage::default(); + b.insert("foo", StringValue::from("1")); + assert_eq!("foo=1", b.to_string()); + + // "URL encoded value" + let mut b = Baggage::default(); + b.insert("foo", StringValue::from("1=1")); + assert_eq!("foo=1%3D1", b.to_string()); + + // "single member empty value with properties" + let mut b = Baggage::default(); + b.insert_with_metadata( + "foo", + StringValue::from(""), + BaggageMetadata::from("red;state=on"), + ); + assert_eq!("foo=;red;state=on", b.to_string()); + + // "single member with properties" + let mut b = Baggage::default(); + b.insert_with_metadata("foo", StringValue::from("1"), "red;state=on;z=z=z"); + assert_eq!("foo=1;red;state=on;z=z=z", b.to_string()); + + // "two members with properties" + let mut b = Baggage::default(); + b.insert_with_metadata("foo", StringValue::from("1"), "red;state=on"); + b.insert_with_metadata("bar", StringValue::from("2"), "yellow"); + assert_eq!("bar=2;yellow,foo=1;red;state=on", b.to_string()); + + // assert it implements Display + let _ = format!("{}", b); + } }