Skip to main content

kanade_shared/wire/
result.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Serialize, Deserialize, Debug, Clone)]
4pub struct ExecResult {
5    pub request_id: String,
6    pub pc_id: String,
7    pub exit_code: i32,
8    pub stdout: String,
9    pub stderr: String,
10    pub started_at: chrono::DateTime<chrono::Utc>,
11    pub finished_at: chrono::DateTime<chrono::Utc>,
12    /// v0.13: the manifest id that produced this result. Sourced
13    /// from `Command.id` (which is the YAML `manifest.id`, e.g.
14    /// `"inventory-hw"`). Distinct from the per-deploy UUID stored
15    /// in `Command.job_id`. The results projector uses this to
16    /// look up the manifest's `inventory:` hint and upsert
17    /// `inventory_facts` rows for inventory-tagged jobs.
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub manifest_id: Option<String>,
20}
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25    use chrono::TimeZone;
26
27    #[test]
28    fn exec_result_round_trips_through_json() {
29        let t0 = chrono::Utc.with_ymd_and_hms(2026, 5, 16, 0, 0, 0).unwrap();
30        let t1 = chrono::Utc.with_ymd_and_hms(2026, 5, 16, 0, 0, 5).unwrap();
31        let r = ExecResult {
32            request_id: "req-1".into(),
33            pc_id: "minipc".into(),
34            exit_code: 0,
35            stdout: "hello\n".into(),
36            stderr: String::new(),
37            started_at: t0,
38            finished_at: t1,
39            manifest_id: Some("inventory-hw".into()),
40        };
41        let json = serde_json::to_string(&r).unwrap();
42        let back: ExecResult = serde_json::from_str(&json).unwrap();
43        assert_eq!(back.request_id, r.request_id);
44        assert_eq!(back.exit_code, r.exit_code);
45        assert_eq!(back.stdout, r.stdout);
46        assert_eq!(back.started_at, t0);
47        assert_eq!(back.finished_at, t1);
48        assert_eq!(back.manifest_id.as_deref(), Some("inventory-hw"));
49    }
50
51    #[test]
52    fn exec_result_without_manifest_id_decodes() {
53        // Older agents (pre-0.13) sent ExecResult with no manifest_id field.
54        let json = r#"{
55            "request_id":"r","pc_id":"x","exit_code":0,
56            "stdout":"","stderr":"",
57            "started_at":"2026-05-16T00:00:00Z",
58            "finished_at":"2026-05-16T00:00:00Z"
59        }"#;
60        let r: ExecResult = serde_json::from_str(json).unwrap();
61        assert_eq!(r.manifest_id, None);
62    }
63}