use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::error::WyrdError;
use crate::vala::ids::{SpanId, TraceId};
use super::{InstrumentationScope, Resource, SpanEvent, SpanLink};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SpanRecord {
pub trace_id: TraceId,
pub span_id: SpanId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_span_id: Option<SpanId>,
#[serde(default)]
pub flags: u32,
#[serde(default)]
pub trace_state: String,
pub name: String,
pub kind: SpanKind,
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub duration_ms: u64,
pub status: SpanStatus,
#[serde(default)]
pub attributes: serde_json::Map<String, serde_json::Value>,
#[serde(default)]
pub dropped_attributes_count: u32,
#[serde(default)]
pub events: Vec<SpanEvent>,
#[serde(default)]
pub dropped_events_count: u32,
#[serde(default)]
pub links: Vec<SpanLink>,
#[serde(default)]
pub dropped_links_count: u32,
pub scope: InstrumentationScope,
pub resource: Resource,
}
impl SpanRecord {
#[must_use]
pub fn service_name(&self) -> &str {
&self.resource.service_name
}
pub fn validate(&self) -> Result<(), WyrdError> {
if self.name.is_empty() {
return Err(WyrdError::Validation {
message: "span.name must be non-empty".to_string(),
details: serde_json::Value::Null,
});
}
if self.name.len() > 256 {
return Err(WyrdError::Validation {
message: format!("span.name length must be <= 256, got {}", self.name.len()),
details: serde_json::Value::Null,
});
}
if self.attributes.len() > 256 {
return Err(WyrdError::Validation {
message: format!(
"span.attributes count must be <= 256, got {}",
self.attributes.len()
),
details: serde_json::Value::Null,
});
}
if self.end_time < self.start_time {
return Err(WyrdError::Validation {
message: format!(
"span.end_time {:?} < start_time {:?}",
self.end_time, self.start_time
),
details: serde_json::Value::Null,
});
}
let derived = super::duration_ms_from_timestamps(self.start_time, self.end_time);
if self.duration_ms != derived {
return Err(WyrdError::Validation {
message: format!(
"span.duration_ms {} != end_time - start_time = {}",
self.duration_ms, derived
),
details: serde_json::Value::Null,
});
}
if self.trace_state.len() > 512 {
return Err(WyrdError::Validation {
message: format!(
"span.trace_state length must be <= 512, got {}",
self.trace_state.len()
),
details: serde_json::Value::Null,
});
}
for (index, event) in self.events.iter().enumerate() {
event.validate().map_err(|error| match error {
WyrdError::Validation { message, .. } => WyrdError::Validation {
message: format!("events[{index}]: {message}"),
details: serde_json::Value::Null,
},
other => other,
})?;
if event.timestamp < self.start_time || event.timestamp > self.end_time {
return Err(WyrdError::Validation {
message: format!(
"events[{index}].timestamp {:?} outside span window [{:?}, {:?}]",
event.timestamp, self.start_time, self.end_time
),
details: serde_json::Value::Null,
});
}
}
for (index, link) in self.links.iter().enumerate() {
link.validate().map_err(|error| match error {
WyrdError::Validation { message, .. } => WyrdError::Validation {
message: format!("links[{index}]: {message}"),
details: serde_json::Value::Null,
},
other => other,
})?;
}
self.resource.validate()?;
self.scope.validate()?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SpanKind {
Internal,
Server,
Client,
Producer,
Consumer,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(tag = "code", rename_all = "snake_case", deny_unknown_fields)]
pub enum SpanStatus {
Unset,
Ok,
Error {
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
},
}