diff --git a/libdd-common/src/lib.rs b/libdd-common/src/lib.rs index 0d841543fe..f1346c9a5e 100644 --- a/libdd-common/src/lib.rs +++ b/libdd-common/src/lib.rs @@ -86,10 +86,6 @@ pub mod header { #![allow(clippy::declare_interior_mutable_const)] use hyper::{header::HeaderName, http::HeaderValue}; - // These strings are defined separately to be used in context where &str are used to represent - // headers (e.g. SendData) while keeping a single source of truth. - pub const DATADOG_SEND_REAL_HTTP_STATUS_STR: &str = "datadog-send-real-http-status"; - pub const DATADOG_TRACE_COUNT_STR: &str = "x-datadog-trace-count"; pub const APPLICATION_MSGPACK_STR: &str = "application/msgpack"; pub const APPLICATION_PROTOBUF_STR: &str = "application/x-protobuf"; @@ -101,7 +97,7 @@ pub mod header { /// If this is not set then the agent will always return a 200 regardless if the payload is /// dropped. pub const DATADOG_SEND_REAL_HTTP_STATUS: HeaderName = - HeaderName::from_static(DATADOG_SEND_REAL_HTTP_STATUS_STR); + HeaderName::from_static("datadog-send-real-http-status"); pub const DATADOG_API_KEY: HeaderName = HeaderName::from_static("dd-api-key"); pub const APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); pub const APPLICATION_MSGPACK: HeaderValue = HeaderValue::from_static(APPLICATION_MSGPACK_STR); diff --git a/libdd-data-pipeline/src/stats_exporter.rs b/libdd-data-pipeline/src/stats_exporter.rs index 8e2768dbec..b1b235c995 100644 --- a/libdd-data-pipeline/src/stats_exporter.rs +++ b/libdd-data-pipeline/src/stats_exporter.rs @@ -3,7 +3,6 @@ use std::{ borrow::Borrow, - collections::HashMap, sync::{ atomic::{AtomicU64, Ordering}, Arc, Mutex, @@ -84,11 +83,11 @@ impl StatsExporter { } let body = rmp_serde::encode::to_vec_named(&payload)?; - let mut headers: HashMap<&'static str, String> = self.meta.borrow().into(); + let mut headers: http::HeaderMap = self.meta.borrow().into(); headers.insert( - http::header::CONTENT_TYPE.as_str(), - libdd_common::header::APPLICATION_MSGPACK_STR.to_string(), + http::header::CONTENT_TYPE, + libdd_common::header::APPLICATION_MSGPACK, ); let result = send_with_retry( diff --git a/libdd-data-pipeline/src/trace_exporter/agent_response.rs b/libdd-data-pipeline/src/trace_exporter/agent_response.rs index df3a3ae929..02b4ae3fa6 100644 --- a/libdd-data-pipeline/src/trace_exporter/agent_response.rs +++ b/libdd-data-pipeline/src/trace_exporter/agent_response.rs @@ -1,11 +1,13 @@ // Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 +use http::HeaderName; use std::sync::Arc; use arc_swap::ArcSwap; -pub const DATADOG_RATES_PAYLOAD_VERSION_HEADER: &str = "datadog-rates-payload-version"; +pub const DATADOG_RATES_PAYLOAD_VERSION: HeaderName = + HeaderName::from_static("datadog-rates-payload-version"); /// `AgentResponse` structure holds agent response information upon successful request. #[derive(Debug, PartialEq)] diff --git a/libdd-data-pipeline/src/trace_exporter/mod.rs b/libdd-data-pipeline/src/trace_exporter/mod.rs index 4169a89323..42bd17dcb8 100644 --- a/libdd-data-pipeline/src/trace_exporter/mod.rs +++ b/libdd-data-pipeline/src/trace_exporter/mod.rs @@ -19,7 +19,7 @@ use crate::pausable_worker::PausableWorker; use crate::stats_exporter::StatsExporter; use crate::telemetry::{SendPayloadTelemetry, TelemetryClient}; use crate::trace_exporter::agent_response::{ - AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION_HEADER, + AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION, }; use crate::trace_exporter::error::{InternalErrorKind, RequestError, TraceExporterError}; use crate::{ @@ -45,7 +45,7 @@ use libdd_trace_utils::trace_utils::TracerHeaderTags; use std::io; use std::sync::{Arc, Mutex}; use std::time::Duration; -use std::{borrow::Borrow, collections::HashMap, str::FromStr}; +use std::{borrow::Borrow, str::FromStr}; use tokio::runtime::Runtime; use tracing::{debug, error, warn}; @@ -148,8 +148,8 @@ impl<'a> From<&'a TracerMetadata> for TracerHeaderTags<'a> { } } -impl<'a> From<&'a TracerMetadata> for HashMap<&'static str, String> { - fn from(tags: &'a TracerMetadata) -> HashMap<&'static str, String> { +impl<'a> From<&'a TracerMetadata> for http::HeaderMap { + fn from(tags: &'a TracerMetadata) -> http::HeaderMap { TracerHeaderTags::from(tags).into() } } @@ -563,7 +563,7 @@ impl TraceExporter { &self, endpoint: &Endpoint, mp_payload: Vec, - headers: HashMap<&'static str, String>, + headers: http::HeaderMap, chunks: usize, chunks_dropped_p0: usize, ) -> Result { @@ -738,7 +738,7 @@ impl TraceExporter { match ( status.is_success(), self.agent_payload_response_version.as_ref(), - response.headers().get(DATADOG_RATES_PAYLOAD_VERSION_HEADER), + response.headers().get(DATADOG_RATES_PAYLOAD_VERSION), ) { (false, _, _) => { // If the status is not success, the rates are considered unchanged @@ -876,7 +876,6 @@ mod tests { use libdd_trace_utils::msgpack_encoder; use libdd_trace_utils::span::v04::SpanBytes; use libdd_trace_utils::span::v05; - use std::collections::HashMap; use std::net; use std::time::Duration; use tokio::time::sleep; @@ -920,7 +919,7 @@ mod tests { ..Default::default() }; - let hashmap: HashMap<&'static str, String> = (&tracer_tags).into(); + let hashmap: http::HeaderMap = (&tracer_tags).into(); assert_eq!(hashmap.get("datadog-meta-tracer-version").unwrap(), "v0.1"); assert_eq!(hashmap.get("datadog-meta-lang").unwrap(), "rust"); diff --git a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs index c8083fe6c2..2a257c7e82 100644 --- a/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs +++ b/libdd-data-pipeline/src/trace_exporter/trace_serializer.rs @@ -2,27 +2,26 @@ // SPDX-License-Identifier: Apache-2.0 use crate::trace_exporter::agent_response::{ - AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION_HEADER, + AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION, }; use crate::trace_exporter::error::TraceExporterError; use crate::trace_exporter::TraceExporterOutputFormat; -use http::header::CONTENT_TYPE; +use http::{header::CONTENT_TYPE, HeaderMap, HeaderValue}; use libdd_common::header::{ - APPLICATION_MSGPACK_STR, DATADOG_SEND_REAL_HTTP_STATUS_STR, DATADOG_TRACE_COUNT_STR, + APPLICATION_MSGPACK, DATADOG_SEND_REAL_HTTP_STATUS, DATADOG_TRACE_COUNT, }; use libdd_trace_utils::msgpack_decoder::decode::error::DecodeError; use libdd_trace_utils::msgpack_encoder; use libdd_trace_utils::span::{v04::Span, TraceData}; use libdd_trace_utils::trace_utils::{self, TracerHeaderTags}; use libdd_trace_utils::tracer_payload; -use std::collections::HashMap; /// Prepared traces payload ready for sending to the agent pub(super) struct PreparedTracesPayload { /// Serialized msgpack payload pub data: Vec, /// HTTP headers for the request - pub headers: HashMap<&'static str, String>, + pub headers: HeaderMap, /// Number of trace chunks pub chunk_count: usize, } @@ -78,20 +77,16 @@ impl<'a> TraceSerializer<'a> { } /// Build HTTP headers for traces request - fn build_traces_headers( - &self, - header_tags: TracerHeaderTags, - chunk_count: usize, - ) -> HashMap<&'static str, String> { - let mut headers: HashMap<&'static str, String> = header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS_STR, "1".to_string()); - headers.insert(DATADOG_TRACE_COUNT_STR, chunk_count.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + fn build_traces_headers(&self, header_tags: TracerHeaderTags, chunk_count: usize) -> HeaderMap { + let mut headers: HeaderMap = header_tags.into(); + headers.reserve(4); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1")); + headers.insert(DATADOG_TRACE_COUNT, chunk_count.into()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); if let Some(agent_payload_response_version) = &self.agent_payload_response_version { - headers.insert( - DATADOG_RATES_PAYLOAD_VERSION_HEADER, - agent_payload_response_version.header_value(), - ); + // should never fail, as the version should only contain visible ascii chars + let _ = HeaderValue::try_from(agent_payload_response_version.header_value()) + .map(|v| headers.insert(DATADOG_RATES_PAYLOAD_VERSION, v)); } headers } @@ -115,9 +110,7 @@ mod tests { use super::*; use crate::trace_exporter::agent_response::AgentResponsePayloadVersion; use http::header::CONTENT_TYPE; - use libdd_common::header::{ - APPLICATION_MSGPACK_STR, DATADOG_SEND_REAL_HTTP_STATUS_STR, DATADOG_TRACE_COUNT_STR, - }; + use libdd_common::header::APPLICATION_MSGPACK_STR; use libdd_tinybytes::BytesString; use libdd_trace_utils::span::v04::SpanBytes; use libdd_trace_utils::trace_utils::TracerHeaderTags; @@ -179,12 +172,9 @@ mod tests { let headers = serializer.build_traces_headers(header_tags, 3); // Check basic headers are present - assert_eq!(headers.get(DATADOG_SEND_REAL_HTTP_STATUS_STR).unwrap(), "1"); - assert_eq!(headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "3"); - assert_eq!( - headers.get(CONTENT_TYPE.as_str()).unwrap(), - APPLICATION_MSGPACK_STR - ); + assert_eq!(headers.get(DATADOG_SEND_REAL_HTTP_STATUS).unwrap(), "1"); + assert_eq!(headers.get(DATADOG_TRACE_COUNT).unwrap(), "3"); + assert_eq!(headers.get(CONTENT_TYPE).unwrap(), APPLICATION_MSGPACK_STR); // Check tracer metadata headers are present assert_eq!(headers.get("datadog-meta-lang").unwrap(), "rust"); @@ -212,8 +202,8 @@ mod tests { let headers = serializer.build_traces_headers(header_tags, 2); // Check that agent payload version header is included - assert!(headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION_HEADER)); - assert_eq!(headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "2"); + assert!(headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION)); + assert_eq!(headers.get(DATADOG_TRACE_COUNT).unwrap(), "2"); } #[test] @@ -346,7 +336,7 @@ mod tests { assert!(!prepared.headers.is_empty()); // Check headers - assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "2"); + assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT).unwrap(), "2"); assert_eq!(prepared.headers.get("datadog-meta-lang").unwrap(), "rust"); } @@ -377,9 +367,7 @@ mod tests { let prepared = result.unwrap(); assert_eq!(prepared.chunk_count, 1); - assert!(prepared - .headers - .contains_key(DATADOG_RATES_PAYLOAD_VERSION_HEADER)); + assert!(prepared.headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION)); } #[test] @@ -394,7 +382,7 @@ mod tests { let prepared = result.unwrap(); assert_eq!(prepared.chunk_count, 0); assert!(!prepared.data.is_empty()); // Even empty traces result in some serialized data - assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT_STR).unwrap(), "0"); + assert_eq!(prepared.headers.get(DATADOG_TRACE_COUNT).unwrap(), "0"); } #[test] diff --git a/libdd-trace-utils/src/send_data/mod.rs b/libdd-trace-utils/src/send_data/mod.rs index 3115c67d8d..0220b6591a 100644 --- a/libdd-trace-utils/src/send_data/mod.rs +++ b/libdd-trace-utils/src/send_data/mod.rs @@ -10,11 +10,11 @@ use crate::tracer_payload::TracerPayloadCollection; use anyhow::{anyhow, Context}; use futures::stream::FuturesUnordered; use futures::StreamExt; -use http::header::CONTENT_TYPE; +use http::{header::CONTENT_TYPE, HeaderMap, HeaderValue}; use libdd_common::{ header::{ - APPLICATION_MSGPACK_STR, APPLICATION_PROTOBUF_STR, DATADOG_SEND_REAL_HTTP_STATUS_STR, - DATADOG_TRACE_COUNT_STR, + APPLICATION_MSGPACK, APPLICATION_PROTOBUF, DATADOG_SEND_REAL_HTTP_STATUS, + DATADOG_TRACE_COUNT, }, Connect, Endpoint, GenericHttpClient, }; @@ -68,7 +68,7 @@ pub struct SendData { pub(crate) tracer_payloads: TracerPayloadCollection, pub(crate) size: usize, // have a rough size estimate to force flushing if it's large target: Endpoint, - headers: HashMap<&'static str, String>, + headers: HeaderMap, retry_strategy: RetryStrategy, #[cfg(feature = "compression")] compression: Compression, @@ -85,7 +85,7 @@ pub struct SendDataBuilder { pub(crate) tracer_payloads: TracerPayloadCollection, pub(crate) size: usize, target: Endpoint, - headers: HashMap<&'static str, String>, + headers: HeaderMap, retry_strategy: RetryStrategy, #[cfg(feature = "compression")] compression: Compression, @@ -98,8 +98,8 @@ impl SendDataBuilder { tracer_header_tags: TracerHeaderTags, target: &Endpoint, ) -> SendDataBuilder { - let mut headers: HashMap<&'static str, String> = tracer_header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS_STR, "1".to_string()); + let mut headers: HeaderMap = tracer_header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1")); SendDataBuilder { tracer_payloads: tracer_payload, size, @@ -160,8 +160,8 @@ impl SendData { tracer_header_tags: TracerHeaderTags, target: &Endpoint, ) -> SendData { - let mut headers: HashMap<&'static str, String> = tracer_header_tags.into(); - headers.insert(DATADOG_SEND_REAL_HTTP_STATUS_STR, "1".to_string()); + let mut headers: HeaderMap = tracer_header_tags.into(); + headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1")); SendData { tracer_payloads: tracer_payload, size, @@ -243,7 +243,7 @@ impl SendData { &self, chunks: u64, payload: Vec, - headers: HashMap<&'static str, String>, + headers: HeaderMap, http_client: &GenericHttpClient, endpoint: Option<&Endpoint>, ) -> (SendWithRetryResult, u64, u64) { @@ -268,7 +268,7 @@ impl SendData { } #[cfg(feature = "compression")] - fn compress_payload(&self, payload: Vec, headers: &mut HashMap<&str, String>) -> Vec { + fn compress_payload(&self, payload: Vec, headers: &mut HeaderMap) -> Vec { match self.compression { Compression::Zstd(level) => { let result = (|| -> std::io::Result> { @@ -279,7 +279,10 @@ impl SendData { match result { Ok(compressed_payload) => { - headers.insert("Content-Encoding", "zstd".to_string()); + headers.insert( + http::header::CONTENT_ENCODING, + HeaderValue::from_static("zstd"), + ); compressed_payload } Err(_) => payload, @@ -317,7 +320,7 @@ impl SendData { #[cfg(not(feature = "compression"))] let final_payload = serialized_trace_payload; - request_headers.insert(CONTENT_TYPE.as_str(), APPLICATION_PROTOBUF_STR.to_string()); + request_headers.insert(CONTENT_TYPE, APPLICATION_PROTOBUF); let (response, bytes_sent, chunks) = self .send_payload( @@ -351,8 +354,9 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(tracer_payload.chunks.len()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT_STR, chunks.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + headers.reserve(2); + headers.insert(DATADOG_TRACE_COUNT, chunks.into()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = match rmp_serde::to_vec_named(tracer_payload) { Ok(p) => p, @@ -372,8 +376,9 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT_STR, chunks.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + headers.reserve(2); + headers.insert(DATADOG_TRACE_COUNT, chunks.into()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = msgpack_encoder::v04::to_vec(payload); @@ -389,8 +394,9 @@ impl SendData { #[allow(clippy::unwrap_used)] let chunks = u64::try_from(self.tracer_payloads.size()).unwrap(); let mut headers = self.headers.clone(); - headers.insert(DATADOG_TRACE_COUNT_STR, chunks.to_string()); - headers.insert(CONTENT_TYPE.as_str(), APPLICATION_MSGPACK_STR.to_string()); + headers.reserve(2); + headers.insert(DATADOG_TRACE_COUNT, chunks.into()); + headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK); let payload = match rmp_serde::to_vec(payload) { Ok(p) => p, @@ -573,8 +579,8 @@ mod tests { assert_eq!(data.target.api_key, None); assert_eq!(data.target.url.path(), "/foo/bar"); - for (key, value) in HashMap::from(header_tags) { - assert_eq!(data.headers.get(key).unwrap(), &value); + for (key, value) in &HeaderMap::from(header_tags) { + assert_eq!(data.headers.get(key).unwrap(), value); } } @@ -679,7 +685,7 @@ mod tests { let mock = server .mock_async(|when, then| { when.method(POST) - .header(DATADOG_TRACE_COUNT_STR, "1") + .header(DATADOG_TRACE_COUNT.as_str(), "1") .header("Content-type", "application/msgpack") .header("datadog-meta-lang", header_tags.lang) .header( @@ -739,7 +745,7 @@ mod tests { let mock = server .mock_async(|when, then| { when.method(POST) - .header(DATADOG_TRACE_COUNT_STR, "1") + .header(DATADOG_TRACE_COUNT.as_str(), "1") .header("Content-type", "application/msgpack") .header("datadog-meta-lang", header_tags.lang) .header( @@ -928,7 +934,7 @@ mod tests { let mock = server .mock_async(|when, then| { when.method(POST) - .header(DATADOG_TRACE_COUNT_STR, "2") + .header(DATADOG_TRACE_COUNT.as_str(), "2") .header("Content-type", "application/msgpack") .header("datadog-meta-lang", header_tags.lang) .header( diff --git a/libdd-trace-utils/src/send_with_retry/mod.rs b/libdd-trace-utils/src/send_with_retry/mod.rs index bb9c3ae662..28b24cf11d 100644 --- a/libdd-trace-utils/src/send_with_retry/mod.rs +++ b/libdd-trace-utils/src/send_with_retry/mod.rs @@ -8,9 +8,9 @@ mod retry_strategy; pub use retry_strategy::{RetryBackoffType, RetryStrategy}; use bytes::Bytes; -use http::Method; +use http::{HeaderMap, Method}; use libdd_common::{http_common, Connect, Endpoint, GenericHttpClient, HttpRequestBuilder}; -use std::{collections::HashMap, time::Duration}; +use std::time::Duration; use tracing::{debug, error}; pub type Attempts = u32; @@ -97,14 +97,17 @@ impl std::error::Error for RequestError {} /// # use libdd_common::Endpoint; /// # use libdd_common::http_common::new_default_client; /// # use libdd_trace_utils::send_with_retry::*; -/// # use std::collections::HashMap; /// # async fn run() -> SendWithRetryResult { /// let payload: Vec = vec![0, 1, 2, 3]; /// let target = Endpoint { /// url: "localhost:8126/v04/traces".parse::().unwrap(), /// ..Endpoint::default() /// }; -/// let headers = HashMap::from([("Content-type", "application/msgpack".to_string())]); +/// let mut headers = http::HeaderMap::new(); +/// headers.insert( +/// http::HeaderName::from_static("content-type"), +/// http::HeaderValue::from_static("application/msgpack"), +/// ); /// let retry_strategy = RetryStrategy::new(3, 10, RetryBackoffType::Exponential, Some(5)); /// let client = new_default_client(); /// send_with_retry(&client, &target, payload, &headers, &retry_strategy).await @@ -114,7 +117,7 @@ pub async fn send_with_retry( client: &GenericHttpClient, target: &Endpoint, payload: Vec, - headers: &HashMap<&'static str, String>, + headers: &HeaderMap, retry_strategy: &RetryStrategy, ) -> SendWithRetryResult { let mut request_attempt = 0; @@ -142,7 +145,7 @@ pub async fn send_with_retry( .or(Err(SendWithRetryError::Build(request_attempt)))? .method(Method::POST); for (key, value) in headers { - req = req.header(*key, value.clone()); + req = req.header(key, value); } match send_request( @@ -285,7 +288,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; @@ -334,7 +337,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; @@ -383,7 +386,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; @@ -432,7 +435,7 @@ mod tests { &client, &target_endpoint, vec![0, 1, 2, 3], - &HashMap::new(), + &HeaderMap::new(), &strategy, ) .await; diff --git a/libdd-trace-utils/src/tracer_header_tags.rs b/libdd-trace-utils/src/tracer_header_tags.rs index cae7fc53d1..45d30fa0e4 100644 --- a/libdd-trace-utils/src/tracer_header_tags.rs +++ b/libdd-trace-utils/src/tracer_header_tags.rs @@ -1,10 +1,8 @@ // Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -use http::HeaderMap; -use http::HeaderValue; +use http::{HeaderMap, HeaderName, HeaderValue}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; macro_rules! parse_string_header { ( @@ -41,58 +39,79 @@ pub struct TracerHeaderTags<'a> { pub dropped_p0_spans: usize, } -impl<'a> From> for HashMap<&'static str, String> { - fn from(tags: TracerHeaderTags<'a>) -> HashMap<&'static str, String> { - let mut headers = HashMap::from([ - ("datadog-meta-lang", tags.lang.to_string()), - ("datadog-meta-lang-version", tags.lang_version.to_string()), - ( - "datadog-meta-lang-interpreter", - tags.lang_interpreter.to_string(), - ), - ( - "datadog-meta-lang-interpreter-vendor", - tags.lang_vendor.to_string(), - ), - ( - "datadog-meta-tracer-version", - tags.tracer_version.to_string(), - ), - ("datadog-container-id", tags.container_id.to_string()), - ( - "datadog-client-computed-stats", - if tags.client_computed_stats { - "true".to_string() - } else { - String::new() - }, - ), - ( - "datadog-client-computed-top-level", - if tags.client_computed_top_level { - "true".to_string() - } else { - String::new() - }, - ), - ( - "datadog-client-dropped-p0-traces", - if tags.dropped_p0_traces > 0 { - tags.dropped_p0_traces.to_string() - } else { - String::new() - }, - ), - ( - "datadog-client-dropped-p0-spans", - if tags.dropped_p0_spans > 0 { - tags.dropped_p0_spans.to_string() - } else { - String::new() - }, - ), - ]); - headers.retain(|_, v| !v.is_empty()); +impl<'a> From> for HeaderMap { + fn from(tags: TracerHeaderTags<'a>) -> HeaderMap { + let mut headers = HeaderMap::with_capacity(10); + fn try_insert( + h: &mut HeaderMap, + key: HeaderName, + v: impl TryInto + AsRef<[u8]>, + ) { + if v.as_ref().is_empty() { + return; + } + if let Ok(v) = v.try_into() { + h.insert(key, v); + } + } + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang"), + tags.lang, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang-version"), + tags.lang_version, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang-interpreter"), + tags.lang_interpreter, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-lang-interpreter-vendor"), + tags.lang_vendor, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-meta-tracer-version"), + tags.tracer_version, + ); + try_insert( + &mut headers, + HeaderName::from_static("datadog-container-id"), + tags.container_id, + ); + if tags.client_computed_stats { + try_insert( + &mut headers, + HeaderName::from_static("datadog-client-computed-stats"), + HeaderValue::from_static("true"), + ); + } + if tags.client_computed_top_level { + try_insert( + &mut headers, + HeaderName::from_static("datadog-client-computed-top-level"), + HeaderValue::from_static("true"), + ); + } + if tags.dropped_p0_traces > 0 { + try_insert( + &mut headers, + HeaderName::from_static("datadog-client-dropped-p0-traces"), + tags.dropped_p0_traces.to_string(), + ); + } + if tags.dropped_p0_spans > 0 { + try_insert( + &mut headers, + HeaderName::from_static("datadog-client-dropped-p0-spans"), + tags.dropped_p0_spans.to_string(), + ); + } headers } } @@ -134,7 +153,10 @@ impl<'a> From<&'a HeaderMap> for TracerHeaderTags<'a> { #[cfg(test)] mod tests { use super::*; - use hyper::HeaderMap; + + fn get<'a>(m: &'a HeaderMap, key: &str) -> Option<&'a str> { + m.get(key).and_then(|v| v.to_str().ok()) + } #[test] fn tags_to_hashmap() { @@ -151,29 +173,27 @@ mod tests { dropped_p0_spans: 120, }; - let map: HashMap<&'static str, String> = header_tags.into(); + let map: HeaderMap = header_tags.into(); assert_eq!(map.len(), 10); - assert_eq!(map.get("datadog-meta-lang").unwrap(), "test-lang"); - assert_eq!(map.get("datadog-meta-lang-version").unwrap(), "2.0"); - assert_eq!( - map.get("datadog-meta-lang-interpreter").unwrap(), - "interpreter" - ); + assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); + assert_eq!(get(&map, "datadog-meta-lang-version"), Some("2.0")); assert_eq!( - map.get("datadog-meta-lang-interpreter-vendor").unwrap(), - "vendor" + get(&map, "datadog-meta-lang-interpreter"), + Some("interpreter") ); - assert_eq!(map.get("datadog-meta-tracer-version").unwrap(), "1.0"); - assert_eq!(map.get("datadog-container-id").unwrap(), "id"); assert_eq!( - map.get("datadog-client-computed-top-level").unwrap(), - "true" + get(&map, "datadog-meta-lang-interpreter-vendor"), + Some("vendor") ); - assert_eq!(map.get("datadog-client-computed-stats").unwrap(), "true"); - assert_eq!(map.get("datadog-client-dropped-p0-traces").unwrap(), "12"); - assert_eq!(map.get("datadog-client-dropped-p0-spans").unwrap(), "120"); + assert_eq!(get(&map, "datadog-meta-tracer-version"), Some("1.0")); + assert_eq!(get(&map, "datadog-container-id"), Some("id")); + assert_eq!(get(&map, "datadog-client-computed-top-level"), Some("true")); + assert_eq!(get(&map, "datadog-client-computed-stats"), Some("true")); + assert_eq!(get(&map, "datadog-client-dropped-p0-traces"), Some("12")); + assert_eq!(get(&map, "datadog-client-dropped-p0-spans"), Some("120")); } + #[test] fn tags_to_hashmap_empty_value() { let header_tags = TracerHeaderTags { @@ -189,25 +209,25 @@ mod tests { dropped_p0_traces: 0, }; - let map: HashMap<&'static str, String> = header_tags.into(); + let map: HeaderMap = header_tags.into(); assert_eq!(map.len(), 5); - assert_eq!(map.get("datadog-meta-lang").unwrap(), "test-lang"); - assert_eq!(map.get("datadog-meta-lang-version").unwrap(), "2.0"); + assert_eq!(get(&map, "datadog-meta-lang"), Some("test-lang")); + assert_eq!(get(&map, "datadog-meta-lang-version"), Some("2.0")); assert_eq!( - map.get("datadog-meta-lang-interpreter").unwrap(), - "interpreter" + get(&map, "datadog-meta-lang-interpreter"), + Some("interpreter") ); assert_eq!( - map.get("datadog-meta-lang-interpreter-vendor").unwrap(), - "vendor" + get(&map, "datadog-meta-lang-interpreter-vendor"), + Some("vendor") ); - assert_eq!(map.get("datadog-meta-tracer-version").unwrap(), "1.0"); - assert_eq!(map.get("datadog-container-id"), None); - assert_eq!(map.get("datadog-client-computed-top-level"), None); - assert_eq!(map.get("datadog-client-computed-stats"), None); - assert_eq!(map.get("datadog-client-dropped-p0-traces"), None); - assert_eq!(map.get("datadog-client-dropped-p0-spans"), None); + assert_eq!(get(&map, "datadog-meta-tracer-version"), Some("1.0")); + assert_eq!(get(&map, "datadog-container-id"), None); + assert_eq!(get(&map, "datadog-client-computed-top-level"), None); + assert_eq!(get(&map, "datadog-client-computed-stats"), None); + assert_eq!(get(&map, "datadog-client-dropped-p0-traces"), None); + assert_eq!(get(&map, "datadog-client-dropped-p0-spans"), None); } #[test]