1use std::collections::BTreeMap;
11use std::fmt;
12
13use serde::{Deserialize, Serialize};
14
15use crate::canonical::{CanonicalError, record_canonical_bytes};
16use crate::limits::TRACE_V1;
17use crate::trace_fields::{fixed, span};
18use crate::{Document, DocumentError};
19
20pub const TRACE: &str = "rustev.trace/1";
22
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
26#[serde(untagged)]
27pub enum TraceValue {
28 Str(String),
29 Int(i64),
30 Double(f64),
31 Bool(bool),
32 StrArray(Vec<String>),
33 IntArray(Vec<i64>),
34 DoubleArray(Vec<f64>),
35 BoolArray(Vec<bool>),
36}
37
38impl TraceValue {
39 pub fn as_str(&self) -> Option<&str> {
41 match self {
42 TraceValue::Str(s) => Some(s),
43 _ => None,
44 }
45 }
46
47 pub fn is_well_formed(&self) -> bool {
51 match self {
52 TraceValue::Double(d) => d.is_finite(),
53 TraceValue::DoubleArray(a) => !a.is_empty() && a.iter().all(|d| d.is_finite()),
54 TraceValue::IntArray(a) => !a.is_empty(),
55 TraceValue::BoolArray(a) => !a.is_empty(),
56 _ => true,
57 }
58 }
59}
60
61impl From<&str> for TraceValue {
62 fn from(s: &str) -> Self {
63 TraceValue::Str(s.to_string())
64 }
65}
66
67impl From<String> for TraceValue {
68 fn from(s: String) -> Self {
69 TraceValue::Str(s)
70 }
71}
72
73impl From<i64> for TraceValue {
74 fn from(i: i64) -> Self {
75 TraceValue::Int(i)
76 }
77}
78
79impl From<bool> for TraceValue {
80 fn from(b: bool) -> Self {
81 TraceValue::Bool(b)
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
87#[serde(rename_all = "snake_case")]
88pub enum Mark {
89 Allow,
90 Digest,
91 Truncated,
92}
93
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
96#[serde(deny_unknown_fields)]
97pub struct TraceAttr {
98 pub v: TraceValue,
99 pub r: Mark,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
104#[serde(rename_all = "snake_case")]
105pub enum SpanKind {
106 Internal,
107 Client,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
114#[serde(rename_all = "snake_case")]
115pub enum SpanStatus {
116 Unset,
117 Error,
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
122#[serde(deny_unknown_fields)]
123pub struct TraceEvent {
124 pub name: String,
125 pub time_unix_nanos: u64,
126 pub attributes: BTreeMap<String, TraceAttr>,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
131#[serde(deny_unknown_fields)]
132pub struct TraceScope {
133 pub name: String,
134 pub version: String,
135 pub schema_url: String,
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
140#[serde(deny_unknown_fields)]
141pub struct TraceResource {
142 pub attributes: BTreeMap<String, TraceAttr>,
143 pub schema_url: String,
144 pub scope: TraceScope,
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
149#[serde(deny_unknown_fields)]
150pub struct TraceLine {
151 pub schema: String,
152 pub schema_version: u32,
154 pub trace_id: String,
156 pub span_id: String,
158 pub parent_span_id: Option<String>,
160 pub name: String,
161 pub kind: SpanKind,
162 pub start_unix_nanos: u64,
163 pub end_unix_nanos: u64,
164 pub status: SpanStatus,
165 pub attributes: BTreeMap<String, TraceAttr>,
166 pub events: Vec<TraceEvent>,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub resource: Option<TraceResource>,
169}
170
171crate::document::document!(TraceLine, TRACE, TRACE_V1);
172
173#[derive(Debug, Clone, PartialEq)]
175pub enum TraceLineError {
176 Parse(DocumentError),
178 Invalid { what: String },
180 Canonical(CanonicalError),
182 TooLong { bytes: usize },
184}
185
186impl fmt::Display for TraceLineError {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 match self {
189 TraceLineError::Parse(e) => write!(f, "{e}"),
190 TraceLineError::Invalid { what } => write!(f, "invalid trace line: {what}"),
191 TraceLineError::Canonical(e) => write!(f, "{e}"),
192 TraceLineError::TooLong { bytes } => write!(
193 f,
194 "trace line is {bytes} bytes, at most {}",
195 TRACE_V1.max_bytes
196 ),
197 }
198 }
199}
200
201impl std::error::Error for TraceLineError {}
202
203fn is_hex(s: &str, len: usize) -> bool {
204 s.len() == len
205 && s.bytes()
206 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
207 && s.bytes().any(|b| b != b'0')
208}
209
210fn invalid(what: impl Into<String>) -> TraceLineError {
211 TraceLineError::Invalid { what: what.into() }
212}
213
214impl TraceLine {
215 pub fn parse_line(bytes: &[u8]) -> Result<Self, TraceLineError> {
217 let line = Self::parse(bytes).map_err(TraceLineError::Parse)?;
218 line.check()?;
219 Ok(line)
220 }
221
222 pub fn check(&self) -> Result<(), TraceLineError> {
228 if self.schema_version != crate::trace_fields::SPAN_SCHEMA_VERSION {
229 return Err(invalid("schema_version"));
230 }
231 if !is_hex(&self.trace_id, 32) {
232 return Err(invalid("trace_id"));
233 }
234 if !is_hex(&self.span_id, 16) {
235 return Err(invalid("span_id"));
236 }
237 if let Some(p) = &self.parent_span_id
238 && !is_hex(p, 16)
239 {
240 return Err(invalid("parent_span_id"));
241 }
242 if !span::ALL.contains(&self.name.as_str()) {
243 return Err(invalid("name"));
244 }
245 if self.end_unix_nanos < self.start_unix_nanos {
246 return Err(invalid("end_unix_nanos"));
247 }
248 if self.resource.is_some() != (self.name == span::DECISION) {
249 return Err(invalid("resource"));
250 }
251 let finite = self.attributes.values().all(|a| a.v.is_well_formed())
252 && self
253 .events
254 .iter()
255 .all(|e| e.attributes.values().all(|a| a.v.is_well_formed()));
256 if !finite {
257 return Err(invalid("a non-finite number or an empty non-string array"));
258 }
259 match self.attributes.get(fixed::WITHHELD) {
260 Some(TraceAttr {
261 v: TraceValue::StrArray(_),
262 r: Mark::Allow,
263 }) => {}
264 _ => return Err(invalid(fixed::WITHHELD)),
266 }
267 Ok(())
268 }
269
270 pub fn to_line(&self) -> Result<Vec<u8>, TraceLineError> {
273 self.check()?;
274 let bytes = record_canonical_bytes(self).map_err(TraceLineError::Canonical)?;
275 if bytes.len() > TRACE_V1.max_bytes {
276 return Err(TraceLineError::TooLong { bytes: bytes.len() });
277 }
278 Ok(bytes)
279 }
280}