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