1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Wire-format events emitted by `quire-ci` during a pipeline run.
//!
//! Each event is one JSON object on its own line (JSONL). The
//! envelope holds the producer-side timestamp; [`EventKind`] holds
//! the variant-specific payload. `#[serde(flatten)]` keeps the wire
//! format flat: `{"at_ms": 110, "type": "sh_started", "job_id": …}`.
//!
//! Consumers that need durations pair `*Started` with the matching
//! `*Finished` by `job_id` (plus per-job sh sequence) and read both
//! events' `at_ms` fields.
use serde::{Deserialize, Serialize};
/// Terminal state of a job in the event stream.
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum JobOutcome {
Succeeded,
Failed,
}
/// Outcome of the pipeline run, carried by [`EventKind::RunFinished`].
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RunOutcome {
Succeeded,
PipelineFailure,
}
/// A single event in the run's structured output stream.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Event {
/// Producer-side wall-clock millisecond timestamp.
pub at_ms: i64,
#[serde(flatten)]
pub kind: EventKind,
}
/// The variant-specific payload of an [`Event`].
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum EventKind {
/// A job's run-fn is about to fire.
JobStarted { job_id: String },
/// A job's run-fn returned. `outcome` is `succeeded` if the run-fn
/// returned `Ok`, else `failed`.
JobFinished { job_id: String, outcome: JobOutcome },
/// An sh process is about to spawn.
ShStarted { job_id: String, cmd: String },
/// An sh process exited.
ShFinished { job_id: String, exit_code: i32 },
/// The pipeline run has finished cleanly. Always the last event in
/// the stream — its presence signals that quire-ci ran to completion
/// rather than crashing mid-run.
RunFinished { outcome: RunOutcome },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn job_started_serializes_in_expected_shape() {
let event = Event {
at_ms: 100,
kind: EventKind::JobStarted {
job_id: "build".into(),
},
};
let json = serde_json::to_string(&event).unwrap();
assert_eq!(
json,
r#"{"at_ms":100,"type":"job_started","job_id":"build"}"#
);
}
#[test]
fn job_finished_serializes_in_expected_shape() {
let event = Event {
at_ms: 250,
kind: EventKind::JobFinished {
job_id: "build".into(),
outcome: JobOutcome::Succeeded,
},
};
let json = serde_json::to_string(&event).unwrap();
assert_eq!(
json,
r#"{"at_ms":250,"type":"job_finished","job_id":"build","outcome":"succeeded"}"#
);
}
#[test]
fn job_finished_failed_outcome_serializes_as_failed() {
let event = Event {
at_ms: 250,
kind: EventKind::JobFinished {
job_id: "build".into(),
outcome: JobOutcome::Failed,
},
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains(r#""outcome":"failed""#));
}
#[test]
fn sh_started_serializes_in_expected_shape() {
let event = Event {
at_ms: 110,
kind: EventKind::ShStarted {
job_id: "build".into(),
cmd: "echo hi".into(),
},
};
let json = serde_json::to_string(&event).unwrap();
assert_eq!(
json,
r#"{"at_ms":110,"type":"sh_started","job_id":"build","cmd":"echo hi"}"#
);
}
#[test]
fn sh_finished_serializes_in_expected_shape() {
let event = Event {
at_ms: 190,
kind: EventKind::ShFinished {
job_id: "build".into(),
exit_code: 0,
},
};
let json = serde_json::to_string(&event).unwrap();
assert_eq!(
json,
r#"{"at_ms":190,"type":"sh_finished","job_id":"build","exit_code":0}"#
);
}
#[test]
fn event_round_trips_through_json() {
let event = Event {
at_ms: 110,
kind: EventKind::ShStarted {
job_id: "build".into(),
cmd: "echo hi".into(),
},
};
let json = serde_json::to_string(&event).unwrap();
let decoded: Event = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, event);
}
}