1use crate::id::{RunId, StepInstanceId};
2use chrono::{DateTime, Utc};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
9pub struct WorkflowQueuedEvent {
10 pub run_id: RunId,
11 pub event_type: String,
12 pub timestamp: DateTime<Utc>,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub dsl: Option<String>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub inputs: Option<HashMap<String, Value>>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub initiating_user: Option<String>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
22pub struct WorkflowStartPendingEvent {
23 pub run_id: RunId,
24 pub event_type: String,
25 pub timestamp: DateTime<Utc>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
29pub struct WorkflowRunningEvent {
30 pub run_id: RunId,
31 pub event_type: String,
32 pub timestamp: DateTime<Utc>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
36pub struct WorkflowCompletedEvent {
37 pub run_id: RunId,
38 pub event_type: String,
39 pub timestamp: DateTime<Utc>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
43pub struct WorkflowFailedEvent {
44 pub run_id: RunId,
45 pub event_type: String,
46 pub timestamp: DateTime<Utc>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
50pub struct WorkflowAbortedEvent {
51 pub run_id: RunId,
52 pub event_type: String,
53 pub timestamp: DateTime<Utc>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
57pub struct StepScheduledEvent {
58 pub run_id: RunId,
59 pub step_id: StepInstanceId,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub step_name: Option<String>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub step_type: Option<String>,
64 pub event_type: String,
65 pub step_dsl: Value,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub spec: Option<Value>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub params: Option<Value>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub storage: Option<HashMap<String, Value>>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub test_report_urls: Option<HashMap<String, Value>>,
74 pub timestamp: DateTime<Utc>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
78pub struct StepRunningEvent {
79 pub run_id: RunId,
80 pub step_id: StepInstanceId,
81 pub event_type: String,
82 pub timestamp: DateTime<Utc>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
86pub struct StepCompletedEvent {
87 pub run_id: RunId,
88 pub step_id: StepInstanceId,
89 pub event_type: String,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub runner_id: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub storage_hashes: Option<HashMap<String, Value>>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub artifacts: Option<HashMap<String, Value>>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub test_reports: Option<HashMap<String, Value>>,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub outputs: Option<HashMap<String, Value>>,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub exit_code: Option<i32>,
102 pub timestamp: DateTime<Utc>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
106pub struct StepFailedEvent {
107 pub run_id: RunId,
108 pub step_id: StepInstanceId,
109 pub event_type: String,
110 pub error: String,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub exit_code: Option<i32>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub runner_id: Option<String>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub storage_hashes: Option<HashMap<String, Value>>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub artifacts: Option<HashMap<String, Value>>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub test_reports: Option<HashMap<String, Value>>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub outputs: Option<HashMap<String, Value>>,
123 pub timestamp: DateTime<Utc>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127pub struct StepQueryEvent {
128 pub step_id: StepInstanceId,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
132pub struct StepQueryResponseEvent {
133 pub step_id: StepInstanceId,
134 pub exists: bool,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub status: Option<String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
140pub struct RunnerStepTypeSchema {
141 pub step_type: String,
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub schema: Option<serde_json::Value>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub documentation: Option<String>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
149pub struct RunnerRegisterEvent {
150 pub runner_id: String,
151 pub runner_type: String,
152 pub protocol_version: String,
153 pub nats_subject: String,
154 pub capabilities: Vec<String>,
155 pub step_types: Vec<RunnerStepTypeSchema>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
159pub struct RunnerHeartbeatEvent {
160 pub runner_id: String,
161 pub version: String,
162 pub state: String,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
166pub struct RunnerOfflineEvent {
167 pub runner_id: String,
168 pub event_type: String,
169 pub timestamp: DateTime<Utc>,
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use uuid::Uuid;
176
177 #[test]
178 fn test_runner_register_event_deserializes_from_wire_format() {
179 let wire_json = serde_json::json!({
180 "runner_id": "runner-abc-123",
181 "runner_type": "docker",
182 "protocol_version": "v1",
183 "nats_subject": "stormchaser.v1.runner.docker.runner-abc-123",
184 "capabilities": ["docker", "linux", "container"],
185 "step_types": [
186 {
187 "step_type": "RunContainer",
188 "documentation": "Runs a container using Docker."
189 }
190 ]
191 });
192
193 let event: RunnerRegisterEvent = serde_json::from_value(wire_json).unwrap();
194 assert_eq!(event.runner_id, "runner-abc-123");
195 assert_eq!(event.runner_type, "docker");
196 assert_eq!(event.protocol_version, "v1");
197 assert_eq!(
198 event.nats_subject,
199 "stormchaser.v1.runner.docker.runner-abc-123"
200 );
201 assert_eq!(event.capabilities, vec!["docker", "linux", "container"]);
202 assert_eq!(event.step_types.len(), 1);
203 assert_eq!(event.step_types[0].step_type, "RunContainer");
204 assert!(event.step_types[0].schema.is_none());
205 }
206
207 #[test]
208 fn test_runner_register_event_serializes_correctly() {
209 let event = RunnerRegisterEvent {
210 runner_id: "runner-xyz".to_string(),
211 runner_type: "k8s".to_string(),
212 protocol_version: "v1".to_string(),
213 nats_subject: "stormchaser.v1.runner.k8s.runner-xyz".to_string(),
214 capabilities: vec!["k8s".to_string()],
215 step_types: vec![RunnerStepTypeSchema {
216 step_type: "RunK8sJob".to_string(),
217 schema: None,
218 documentation: Some("Runs a Kubernetes Job.".to_string()),
219 }],
220 };
221
222 let json = serde_json::to_value(&event).unwrap();
223 assert_eq!(json["runner_id"], "runner-xyz");
224 assert_eq!(json["runner_type"], "k8s");
225 assert_eq!(json["protocol_version"], "v1");
226 assert_eq!(json["step_types"][0]["step_type"], "RunK8sJob");
227 assert!(json["step_types"][0].get("schema").is_none());
229 }
230
231 #[test]
232 fn test_step_query_event_has_only_step_id() {
233 let event = StepQueryEvent {
234 step_id: StepInstanceId::new(Uuid::nil()),
235 };
236 let json = serde_json::to_value(&event).unwrap();
237 assert!(json.get("step_id").is_some());
238 assert!(json.get("run_id").is_none());
239 }
240}