Skip to main content

libdd_trace_utils/span/v1/
mod.rs

1// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::span::vec_map::VecMap;
5use crate::span::{BytesData, SliceData, TraceData};
6pub use thin_vec::ThinVec;
7
8/// OpenTelemetry SpanKind values, encoded on the wire as a `uint32`.
9/// Unset or unrecognized kinds default to [`SpanKind::Internal`].
10#[repr(u32)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum SpanKind {
13    #[default]
14    Internal = 1,
15    Server = 2,
16    Client = 3,
17    Producer = 4,
18    Consumer = 5,
19}
20
21impl SpanKind {
22    /// Parses a v0.4 `span.kind` meta value into a [`SpanKind`].
23    /// Unrecognized values map to [`SpanKind::Internal`].
24    pub fn from_meta(s: &str) -> Self {
25        match s {
26            "server" => SpanKind::Server,
27            "client" => SpanKind::Client,
28            "producer" => SpanKind::Producer,
29            "consumer" => SpanKind::Consumer,
30            _ => SpanKind::Internal,
31        }
32    }
33
34    /// Renders this [`SpanKind`] as the lowercase string used for the v0.4 `span.kind` meta value.
35    pub fn as_meta_str(&self) -> &'static str {
36        match self {
37            SpanKind::Internal => "internal",
38            SpanKind::Server => "server",
39            SpanKind::Client => "client",
40            SpanKind::Producer => "producer",
41            SpanKind::Consumer => "consumer",
42        }
43    }
44}
45
46impl From<u32> for SpanKind {
47    /// OTEL SpanKind wire value → enum; unset/unknown → Internal (per OTEL spec).
48    fn from(kind: u32) -> Self {
49        match kind {
50            2 => SpanKind::Server,
51            3 => SpanKind::Client,
52            4 => SpanKind::Producer,
53            5 => SpanKind::Consumer,
54            _ => SpanKind::Internal,
55        }
56    }
57}
58
59/// Typed V1 attribute value.
60/// Replaces v0.4's split `meta` / `metrics` / `meta_struct` maps.
61#[derive(Debug)]
62pub enum AttributeValue<T: TraceData> {
63    String(T::Text),
64    Float(f64),
65    Int(i64),
66    Bool(bool),
67    Bytes(T::Bytes),
68    KeyValue(VecMap<T::Text, AttributeValue<T>>),
69    List(Vec<AttributeValue<T>>),
70}
71
72// Implemented manually rather than derived: `VecMap`'s `PartialEq` is gated to
73// test/test-utils (see its definition) to keep its allocation cost out of casual `==`, so the
74// `KeyValue` variant compares via `slow_compare` instead of relying on that trait impl.
75impl<T: TraceData> PartialEq for AttributeValue<T> {
76    fn eq(&self, other: &Self) -> bool {
77        match (self, other) {
78            (AttributeValue::String(a), AttributeValue::String(b)) => a == b,
79            (AttributeValue::Float(a), AttributeValue::Float(b)) => a == b,
80            (AttributeValue::Int(a), AttributeValue::Int(b)) => a == b,
81            (AttributeValue::Bool(a), AttributeValue::Bool(b)) => a == b,
82            (AttributeValue::Bytes(a), AttributeValue::Bytes(b)) => a == b,
83            (AttributeValue::KeyValue(a), AttributeValue::KeyValue(b)) => a.slow_compare(b),
84            (AttributeValue::List(a), AttributeValue::List(b)) => a == b,
85            _ => false,
86        }
87    }
88}
89
90/// The generic representation of a V1 span.
91///
92/// `T: TraceData` carries the associated text type `T::Text` used for every string field in the
93/// span; `T::Text` can be either owned (e.g. [`BytesString`](libdd_tinybytes::BytesString)) or
94/// borrowed (e.g. `&str`). To define a generic function taking any `Span<T>` you can use the
95/// [`TraceData`] trait:
96/// ```
97/// use libdd_trace_utils::span::{v1::Span, TraceData};
98/// fn foo<T: TraceData>(span: Span<T>) {
99///     let _ = span.attributes.get("foo");
100/// }
101/// ```
102#[derive(Debug, Default)]
103pub struct Span<T: TraceData> {
104    pub service: T::Text,
105    pub name: T::Text,
106    pub resource: T::Text,
107    pub r#type: T::Text,
108    pub span_id: u64,
109    pub parent_id: u64,
110    pub start: i64,
111    pub duration: i64,
112    pub error: bool,
113    pub span_kind: SpanKind,
114    pub env: T::Text,
115    pub version: T::Text,
116    pub component: T::Text,
117    pub attributes: VecMap<T::Text, AttributeValue<T>>,
118    pub span_links: ThinVec<SpanLink<T>>,
119    pub span_events: ThinVec<SpanEvent<T>>,
120}
121
122/// The generic representation of a V1 span link.
123/// `T` is the type used to represent strings in the span link.
124#[derive(Debug, Default)]
125pub struct SpanLink<T: TraceData> {
126    pub trace_id: [u8; 16],
127    pub span_id: u64,
128    pub attributes: VecMap<T::Text, AttributeValue<T>>,
129    pub tracestate: T::Text,
130    pub flags: u32,
131}
132
133/// The generic representation of a V1 span event.
134/// `T` is the type used to represent strings in the span event.
135#[derive(Debug, Default)]
136pub struct SpanEvent<T: TraceData> {
137    pub time_unix_nano: u64,
138    pub name: T::Text,
139    pub attributes: VecMap<T::Text, AttributeValue<T>>,
140}
141
142/// A V1 trace chunk: a group of spans sharing the same `trace_id`, plus chunk-level metadata.
143#[derive(Debug, Default)]
144pub struct TraceChunk<T: TraceData> {
145    pub trace_id: [u8; 16],
146    pub priority: Option<i32>,
147    pub origin: T::Text,
148    pub sampling_mechanism: Option<u32>,
149    pub dropped_trace: bool,
150    pub attributes: VecMap<T::Text, AttributeValue<T>>,
151    pub spans: Vec<Span<T>>,
152}
153
154/// A V1 tracer payload: tracer-level metadata and the trace chunks it carries.
155#[derive(Debug, Default)]
156pub struct TracerPayload<T: TraceData> {
157    pub container_id: T::Text,
158    pub language_name: T::Text,
159    pub language_version: T::Text,
160    pub tracer_version: T::Text,
161    pub runtime_id: T::Text,
162    pub env: T::Text,
163    pub hostname: T::Text,
164    pub app_version: T::Text,
165    pub attributes: VecMap<T::Text, AttributeValue<T>>,
166    pub chunks: Vec<TraceChunk<T>>,
167}
168
169pub type SpanBytes = Span<BytesData>;
170pub type SpanLinkBytes = SpanLink<BytesData>;
171pub type SpanEventBytes = SpanEvent<BytesData>;
172pub type AttributeValueBytes = AttributeValue<BytesData>;
173pub type TraceChunkBytes = TraceChunk<BytesData>;
174pub type TracerPayloadBytes = TracerPayload<BytesData>;
175
176pub type SpanSlice<'a> = Span<SliceData<'a>>;
177pub type SpanLinkSlice<'a> = SpanLink<SliceData<'a>>;
178pub type SpanEventSlice<'a> = SpanEvent<SliceData<'a>>;
179pub type AttributeValueSlice<'a> = AttributeValue<SliceData<'a>>;
180pub type TraceChunkSlice<'a> = TraceChunk<SliceData<'a>>;
181pub type TracerPayloadSlice<'a> = TracerPayload<SliceData<'a>>;
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn span_kind_default_is_internal() {
189        assert_eq!(SpanKind::default(), SpanKind::Internal);
190    }
191
192    #[test]
193    fn span_kind_from_meta() {
194        assert_eq!(SpanKind::from_meta("server"), SpanKind::Server);
195        assert_eq!(SpanKind::from_meta("client"), SpanKind::Client);
196        assert_eq!(SpanKind::from_meta("producer"), SpanKind::Producer);
197        assert_eq!(SpanKind::from_meta("consumer"), SpanKind::Consumer);
198        assert_eq!(SpanKind::from_meta("internal"), SpanKind::Internal);
199        assert_eq!(SpanKind::from_meta(""), SpanKind::Internal);
200        assert_eq!(SpanKind::from_meta("anything-else"), SpanKind::Internal);
201    }
202
203    #[test]
204    fn span_kind_repr_matches_otel_spec() {
205        assert_eq!(SpanKind::Internal as u32, 1);
206        assert_eq!(SpanKind::Server as u32, 2);
207        assert_eq!(SpanKind::Client as u32, 3);
208        assert_eq!(SpanKind::Producer as u32, 4);
209        assert_eq!(SpanKind::Consumer as u32, 5);
210    }
211
212    #[test]
213    fn span_default_has_internal_kind() {
214        let s = SpanBytes::default();
215        assert_eq!(s.span_kind, SpanKind::Internal);
216        assert!(!s.error);
217        assert!(s.attributes.is_empty());
218    }
219}