Skip to main content

stormchaser_model/
events.rs

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        // schema is None, so it should not appear in the output
228        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}