1use std::sync::Arc;
13use std::time::Duration;
14
15#[cfg(feature = "serde")]
16use crate::Metric;
17use crate::MetricSnapshot;
18use crate::OperationAttributes;
19use crate::Stage;
20#[cfg(feature = "serde")]
21use crate::validation::validate_attributes;
22#[cfg(feature = "serde")]
23use crate::validation::validate_metrics;
24
25#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
27#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum Phase {
30 Started,
32 Running,
34 Succeeded,
36 Failed,
38 Cancelled,
40}
41impl Phase {
42 #[must_use]
44 pub const fn as_str(self) -> &'static str {
45 match self {
46 Self::Started => "started",
47 Self::Running => "running",
48 Self::Succeeded => "succeeded",
49 Self::Failed => "failed",
50 Self::Cancelled => "cancelled",
51 }
52 }
53}
54
55#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct Event {
58 operation_id: u64,
59 sequence: u64,
60 phase: Phase,
61 stage: Option<Stage>,
62 attributes: Arc<OperationAttributes>,
63 metrics: Vec<MetricSnapshot>,
64 elapsed: Duration,
65}
66impl Event {
67 pub(crate) fn new(
69 operation_id: u64,
70 sequence: u64,
71 phase: Phase,
72 stage: Option<Stage>,
73 attributes: Arc<OperationAttributes>,
74 metrics: Vec<MetricSnapshot>,
75 elapsed: Duration,
76 ) -> Self {
77 Self {
78 operation_id,
79 sequence,
80 phase,
81 stage,
82 attributes,
83 metrics,
84 elapsed,
85 }
86 }
87 #[must_use]
89 pub const fn operation_id(&self) -> u64 {
90 self.operation_id
91 }
92 #[must_use]
94 pub const fn sequence(&self) -> u64 {
95 self.sequence
96 }
97 #[must_use]
99 pub const fn phase(&self) -> Phase {
100 self.phase
101 }
102 #[must_use]
104 pub const fn stage(&self) -> Option<&Stage> {
105 self.stage.as_ref()
106 }
107 #[must_use]
109 pub fn attributes(&self) -> &OperationAttributes {
110 &self.attributes
111 }
112 #[must_use]
114 pub fn attribute(&self, key: &str) -> Option<&str> {
115 self.attributes.get(key)
116 }
117 #[must_use]
119 pub fn metrics(&self) -> &[MetricSnapshot] {
120 &self.metrics
121 }
122 #[must_use]
124 pub fn metric(&self, metric_id: &str) -> Option<&MetricSnapshot> {
125 self.metrics.iter().find(|metric| metric.id() == metric_id)
126 }
127 #[must_use]
129 pub const fn elapsed(&self) -> Duration {
130 self.elapsed
131 }
132}
133
134#[cfg(feature = "serde")]
136impl serde::Serialize for Event {
137 #[cfg_attr(coverage, inline(never))]
139 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
140 where
141 S: serde::Serializer,
142 {
143 serde::Serialize::serialize(
144 &EventWireRef {
145 operation_id: self.operation_id,
146 sequence: self.sequence,
147 phase: self.phase,
148 stage: self.stage.as_ref(),
149 attributes: self.attributes.as_ref(),
150 metrics: &self.metrics,
151 elapsed: format_duration(self.elapsed),
152 },
153 serializer,
154 )
155 }
156}
157
158#[cfg(feature = "serde")]
160impl<'de> serde::Deserialize<'de> for Event {
161 #[cfg_attr(coverage, inline(never))]
164 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165 where
166 D: serde::Deserializer<'de>,
167 {
168 let wire =
169 <EventWire as serde::Deserialize>::deserialize(deserializer)?;
170 let elapsed =
171 parse_duration(&wire.elapsed).map_err(serde::de::Error::custom)?;
172 validate_wire_event(&wire, elapsed)
173 .map_err(serde::de::Error::custom)?;
174 Ok(Self::new(
175 wire.operation_id,
176 wire.sequence,
177 wire.phase,
178 wire.stage,
179 Arc::new(wire.attributes),
180 wire.metrics,
181 elapsed,
182 ))
183 }
184}
185
186#[cfg(feature = "serde")]
188#[derive(serde::Serialize)]
189struct EventWireRef<'a> {
190 operation_id: u64,
192 sequence: u64,
194 phase: Phase,
196 stage: Option<&'a Stage>,
198 #[serde(skip_serializing_if = "OperationAttributes::is_empty")]
200 attributes: &'a OperationAttributes,
201 metrics: &'a [MetricSnapshot],
203 elapsed: String,
205}
206
207#[cfg(feature = "serde")]
209#[derive(serde::Deserialize)]
210struct EventWire {
211 operation_id: u64,
213 sequence: u64,
215 phase: Phase,
217 stage: Option<Stage>,
219 #[serde(default)]
221 attributes: OperationAttributes,
222 metrics: Vec<MetricSnapshot>,
224 elapsed: String,
226}
227
228#[cfg(feature = "serde")]
230fn format_duration(duration: Duration) -> String {
231 if duration.is_zero() {
232 return "0s".into();
233 }
234 let nanoseconds = duration.as_nanos();
235 for (unit, suffix) in [
236 (3_600_000_000_000_u128, "h"),
237 (60_000_000_000_u128, "m"),
238 (1_000_000_000_u128, "s"),
239 (1_000_000_u128, "ms"),
240 (1_000_u128, "us"),
241 (1_u128, "ns"),
242 ] {
243 if nanoseconds.is_multiple_of(unit) {
244 return format!("{}{}", nanoseconds / unit, suffix);
245 }
246 }
247 unreachable!("nanosecond duration is always divisible by one nanosecond")
248}
249
250#[cfg(feature = "serde")]
252fn parse_duration(text: &str) -> Result<Duration, String> {
253 let (amount, unit) = ["ms", "us", "ns", "h", "m", "s"]
254 .into_iter()
255 .find_map(|unit| text.strip_suffix(unit).map(|amount| (amount, unit)))
256 .ok_or_else(|| {
257 "elapsed must end in h, m, s, ms, us, or ns".to_owned()
258 })?;
259 if amount.is_empty() || !amount.bytes().all(|byte| byte.is_ascii_digit()) {
260 return Err("elapsed amount must be an unsigned integer".into());
261 }
262 let multiplier = match unit {
263 "h" => 3_600_000_000_000_u128,
264 "m" => 60_000_000_000_u128,
265 "s" => 1_000_000_000_u128,
266 "ms" => 1_000_000_u128,
267 "us" => 1_000_u128,
268 "ns" => 1_u128,
269 _ => unreachable!("unit list is exhaustive"),
270 };
271 let nanoseconds = amount
272 .parse::<u128>()
273 .map_err(|_| {
274 "elapsed amount is outside the supported range".to_owned()
275 })?
276 .checked_mul(multiplier)
277 .ok_or_else(|| "elapsed duration overflows".to_owned())?;
278 let seconds = nanoseconds / 1_000_000_000;
279 if seconds > u128::from(u64::MAX) {
280 return Err("elapsed duration overflows".into());
281 }
282 Ok(Duration::new(
283 seconds as u64,
284 (nanoseconds % 1_000_000_000) as u32,
285 ))
286}
287
288#[cfg(feature = "serde")]
290#[cfg_attr(coverage, inline(never))]
291fn validate_wire_event(
292 wire: &EventWire,
293 elapsed: Duration,
294) -> Result<(), String> {
295 if wire.operation_id == 0 {
296 return Err("operation_id must be nonzero".into());
297 }
298 let definitions = wire
299 .metrics
300 .iter()
301 .map(metric_definition)
302 .collect::<Vec<_>>();
303 validate_attributes(&wire.attributes).map_err(|error| error.to_string())?;
304 validate_metrics(&definitions).map_err(|error| error.to_string())?;
305 match wire.phase {
306 Phase::Started => {
307 if wire.sequence != 0
308 || !elapsed.is_zero()
309 || wire.metrics.iter().any(has_dynamic_counts)
310 {
311 return Err(
312 "started event must have sequence 0, zero elapsed, and zero counts".into(),
313 );
314 }
315 }
316 Phase::Running
317 | Phase::Succeeded
318 | Phase::Failed
319 | Phase::Cancelled
320 if wire.sequence == 0 =>
321 {
322 return Err(
323 "non-started event must have a positive sequence".into()
324 );
325 }
326 Phase::Running
327 | Phase::Succeeded
328 | Phase::Failed
329 | Phase::Cancelled => {}
330 }
331 Ok(())
332}
333
334#[cfg(feature = "serde")]
336fn metric_definition(snapshot: &MetricSnapshot) -> Metric {
337 let metric = Metric::new(snapshot.id(), snapshot.name());
338 match snapshot.total() {
339 Some(total) => metric.total(total),
340 None => metric,
341 }
342}
343
344#[cfg(feature = "serde")]
346const fn has_dynamic_counts(snapshot: &MetricSnapshot) -> bool {
347 snapshot.completed() != 0
348 || snapshot.active() != 0
349 || snapshot.succeeded() != 0
350 || snapshot.failed() != 0
351 || snapshot.cancelled() != 0
352}
353
354#[cfg(all(feature = "json-lines", coverage))]
356#[doc(hidden)]
357pub fn __coverage_event_serde() {
358 let value = serde_json::json!({
359 "operation_id": 1,
360 "sequence": 0,
361 "phase": "started",
362 "stage": null,
363 "metrics": [{
364 "id": "tasks",
365 "name": "Tasks",
366 "total": null,
367 "completed": 0,
368 "active": 0,
369 "succeeded": 0,
370 "failed": 0,
371 "cancelled": 0
372 }],
373 "elapsed": "0s"
374 });
375 let text =
376 serde_json::to_string(&value).expect("coverage JSON must serialize");
377 let mut deserializer = serde_json::Deserializer::from_str(&text);
378 let event = <Event as serde::Deserialize>::deserialize(&mut deserializer)
379 .expect("coverage event must deserialize");
380 let wire = EventWire {
381 operation_id: event.operation_id(),
382 sequence: event.sequence(),
383 phase: event.phase(),
384 stage: event.stage().cloned(),
385 attributes: event.attributes().clone(),
386 metrics: event.metrics().to_vec(),
387 elapsed: "0s".into(),
388 };
389 validate_wire_event(&wire, event.elapsed())
390 .expect("coverage event validation must succeed");
391 let mut invalid_wire = wire;
392 invalid_wire.attributes.insert(" ", "invalid");
393 assert!(validate_wire_event(&invalid_wire, event.elapsed()).is_err());
394 let mut output = Vec::new();
395 let mut serializer = serde_json::Serializer::new(&mut output);
396 coverage_serialize_event(&event, &mut serializer)
397 .expect("coverage event must serialize");
398}
399
400#[cfg(all(feature = "json-lines", coverage))]
401#[inline(never)]
402fn coverage_serialize_event(
403 event: &Event,
404 serializer: &mut serde_json::Serializer<&mut Vec<u8>>,
405) -> Result<(), serde_json::Error> {
406 <Event as serde::Serialize>::serialize(event, serializer)
407}