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