Skip to main content

telemetry_rust/test/
mod.rs

1//! Testing utilities for OpenTelemetry integration testing and validation.
2//!
3//! This module provides utilities for testing OpenTelemetry instrumentation,
4//! including trace header manipulation, Jaeger trace data structures for
5//! validation, and HTTP response testing helpers.
6//!
7//! The module contains tools for:
8//! - Parsing and generating trace headers (traceparent, tracestate)
9//! - Deserializing Jaeger trace data for validation
10//! - Testing HTTP responses with trace context
11//! - Generating test trace IDs and span IDs
12
13pub mod jaegar;
14
15use bytes::Bytes;
16use http_body_util::BodyExt;
17use hyper::{
18    HeaderMap, Response,
19    body::{Body, Incoming},
20    header::HeaderValue,
21};
22
23pub use opentelemetry::trace::{SpanId, TraceId};
24
25/// HTTP response wrapper that includes OpenTelemetry trace information.
26///
27/// This struct wraps an HTTP response and provides easy access to the associated
28/// trace ID and span ID for testing and debugging purposes. It's particularly
29/// useful in integration tests where you need to verify trace propagation.
30///
31/// # Example
32///
33/// ```rust
34/// use telemetry_rust::test::{TracedResponse, Traceparent};
35///
36/// async fn send_traced_request() -> TracedResponse<&'static str> {
37///     let traceparent = Traceparent::generate();
38///
39///     // Send request and get response
40///     let resp = hyper::Response::new("Hello world!");
41///
42///     TracedResponse::new(resp, traceparent)
43/// }
44/// ```
45#[derive(Debug)]
46pub struct TracedResponse<T = Incoming> {
47    resp: Response<T>,
48    /// The OpenTelemetry trace ID associated with this response
49    pub trace_id: TraceId,
50    /// The OpenTelemetry span ID associated with this response
51    pub span_id: SpanId,
52}
53
54impl<T> TracedResponse<T> {
55    /// Creates a new traced response from an HTTP response and trace parent information.
56    ///
57    /// # Arguments
58    ///
59    /// - `resp`: The HTTP response to wrap
60    /// - `traceparent`: The trace parent containing trace and span IDs
61    ///
62    /// # Returns
63    ///
64    /// A new [`TracedResponse`] instance
65    pub fn new(resp: Response<T>, traceparent: Traceparent) -> Self {
66        Self {
67            resp,
68            trace_id: traceparent.trace_id,
69            span_id: traceparent.span_id,
70        }
71    }
72
73    /// Consumes the traced response and returns the inner HTTP response.
74    ///
75    /// # Returns
76    ///
77    /// The wrapped [`hyper::Response`] instance.
78    pub async fn into_inner(self) -> Response<T> {
79        self.resp
80    }
81}
82
83impl<E, T: Body<Data = Bytes, Error = E>> TracedResponse<T> {
84    /// Consumes the response and returns the body as bytes.
85    ///
86    /// # Returns
87    ///
88    /// A future that resolves to the response body as [`bytes::Bytes`]
89    ///
90    /// # Errors
91    ///
92    /// Returns an error if the response body cannot be read
93    pub async fn into_bytes(self) -> Result<Bytes, E> {
94        Ok(self.resp.into_body().collect().await?.to_bytes())
95    }
96}
97
98impl<T> std::ops::Deref for TracedResponse<T> {
99    type Target = Response<T>;
100
101    fn deref(&self) -> &Self::Target {
102        &self.resp
103    }
104}
105
106impl<T> std::ops::DerefMut for TracedResponse<T> {
107    fn deref_mut(&mut self) -> &mut Self::Target {
108        &mut self.resp
109    }
110}
111
112/// Enumeration of supported tracing header formats for testing.
113///
114/// This enum represents the different trace context propagation formats
115/// that can be used in HTTP headers for testing distributed tracing scenarios.
116pub enum TracingHeaderKind {
117    /// W3C Trace Context format using the `traceparent` header
118    Traceparent,
119    /// B3 single header format using the `b3` header
120    B3Single,
121    /// B3 multiple header format using separate `X-B3-*` headers
122    B3Multi,
123}
124
125/// A container for OpenTelemetry trace parent information used in testing.
126///
127/// This struct holds a trace ID and span ID pair that represents a trace context
128/// relationship. It's commonly used for generating test trace headers and
129/// validating trace propagation in integration tests.
130///
131/// # Example
132///
133/// ```rust
134/// use telemetry_rust::test::{Traceparent, TracingHeaderKind};
135///
136/// // Generate a new trace parent for testing
137/// let traceparent = Traceparent::generate();
138///
139/// // Create HTTP headers for trace propagation
140/// let headers = traceparent.get_headers(TracingHeaderKind::Traceparent);
141///
142/// // Use in HTTP request testing
143/// let mut req = hyper::Request::new(());
144/// for (key, value) in headers {
145///     if let Some(header_name) = key {
146///         req.headers_mut().insert(header_name, value);
147///     }
148/// }
149/// ```
150pub struct Traceparent {
151    /// The OpenTelemetry trace ID
152    pub trace_id: TraceId,
153    /// The OpenTelemetry span ID
154    pub span_id: SpanId,
155}
156
157impl Traceparent {
158    /// Generates a new random trace parent with random trace and span IDs.
159    ///
160    /// This method creates a new trace parent with randomly generated IDs,
161    /// useful for creating test scenarios with unique trace contexts.
162    ///
163    /// # Returns
164    ///
165    /// A new [`Traceparent`] with randomly generated trace and span IDs
166    ///
167    /// # Examples
168    ///
169    /// ```rust
170    /// use telemetry_rust::test::Traceparent;
171    ///
172    /// let traceparent = Traceparent::generate();
173    /// println!("Trace ID: {}", traceparent.trace_id);
174    /// ```
175    pub fn generate() -> Self {
176        let trace_id = TraceId::from_bytes(rand::random());
177        let span_id = SpanId::from_bytes(rand::random());
178        Self { trace_id, span_id }
179    }
180
181    /// Generates HTTP headers containing trace context in the specified format.
182    ///
183    /// This method creates HTTP headers with trace context information formatted
184    /// according to the specified tracing header kind. This is useful for testing
185    /// trace propagation with different header formats.
186    ///
187    /// # Arguments
188    ///
189    /// - `kind`: The format to use for the trace headers
190    ///
191    /// # Returns
192    ///
193    /// A [`HeaderMap`] containing the appropriately formatted trace headers
194    ///
195    /// # Examples
196    ///
197    /// ```rust
198    /// use telemetry_rust::test::{Traceparent, TracingHeaderKind};
199    ///
200    /// let traceparent = Traceparent::generate();
201    /// let headers = traceparent.get_headers(TracingHeaderKind::Traceparent);
202    /// ```
203    ///
204    /// # Panics
205    ///
206    /// This function will panic if the trace ID or span ID cannot be converted
207    /// to a valid HTTP header value format.
208    pub fn get_headers(&self, kind: TracingHeaderKind) -> HeaderMap {
209        let mut map = HeaderMap::new();
210
211        match kind {
212            TracingHeaderKind::Traceparent => {
213                let value = format!("00-{}-{}-01", self.trace_id, self.span_id);
214                map.append("traceparent", HeaderValue::from_str(&value).unwrap());
215            }
216            TracingHeaderKind::B3Single => {
217                let value = format!("{}-{}-1", self.trace_id, self.span_id);
218                map.append("b3", HeaderValue::from_str(&value).unwrap());
219            }
220            TracingHeaderKind::B3Multi => {
221                map.append(
222                    "X-B3-TraceId",
223                    HeaderValue::from_str(&self.trace_id.to_string()).unwrap(),
224                );
225                map.append(
226                    "X-B3-SpanId",
227                    HeaderValue::from_str(&self.span_id.to_string()).unwrap(),
228                );
229                map.append("X-B3-Sampled", HeaderValue::from_str("1").unwrap());
230            }
231        }
232
233        map
234    }
235}