Skip to main content

har/
render.rs

1use crate::model::CaptureMeta;
2use serde::Serialize;
3
4#[derive(Serialize)]
5pub struct Envelope<T: Serialize> {
6    pub tool: &'static str,
7    pub schema_version: u32,
8    pub command: &'static str,
9    pub capture: CaptureMeta,
10    pub result: T,
11    pub warnings: Vec<String>,
12    pub next_commands: Vec<String>,
13}
14
15impl<T: Serialize> Envelope<T> {
16    pub fn new(command: &'static str, capture: CaptureMeta, result: T) -> Self {
17        Envelope {
18            tool: "wiretrail",
19            schema_version: 1,
20            command,
21            capture,
22            result,
23            warnings: Vec::new(),
24            next_commands: Vec::new(),
25        }
26    }
27
28    pub fn with_next_commands(mut self, cmds: Vec<String>) -> Self {
29        self.next_commands = cmds;
30        self
31    }
32
33    pub fn to_json(&self) -> String {
34        serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
35    }
36}
37
38#[derive(Debug, Clone, Copy)]
39pub enum ExitCode {
40    Clean = 0,
41    Findings = 1,
42    InvalidHar = 2,
43    UnsafeBlocked = 3,
44}
45
46/// Human-readable byte size, e.g. 1.2 MiB.
47pub fn human_bytes(n: i64) -> String {
48    if n < 0 {
49        return "?".to_string();
50    }
51    const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB"];
52    let mut v = n as f64;
53    let mut u = 0;
54    while v >= 1024.0 && u < UNITS.len() - 1 {
55        v /= 1024.0;
56        u += 1;
57    }
58    if u == 0 {
59        format!("{n} B")
60    } else {
61        format!("{v:.1} {}", UNITS[u])
62    }
63}
64
65/// Human-readable milliseconds, e.g. 75.4s or 312ms.
66pub fn human_ms(ms: f64) -> String {
67    if ms >= 1000.0 {
68        format!("{:.1}s", ms / 1000.0)
69    } else {
70        format!("{ms:.0}ms")
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::{Envelope, ExitCode};
77    use crate::model::CaptureMeta;
78    use serde::Serialize;
79
80    #[derive(Serialize)]
81    struct Dummy {
82        n: u32,
83    }
84
85    fn meta() -> CaptureMeta {
86        CaptureMeta {
87            har_version: "1.2".into(),
88            creator: "x".into(),
89            creator_version: "1".into(),
90            browser: None,
91            entry_count: 0,
92            start_ms: None,
93            end_ms: None,
94            duration_ms: 0.0,
95        }
96    }
97
98    #[test]
99    fn serializes_stable_envelope() {
100        let env = Envelope::new("summary", meta(), Dummy { n: 7 });
101        let json = serde_json::to_string(&env).unwrap();
102        assert!(json.contains("\"tool\":\"wiretrail\""));
103        assert!(json.contains("\"schema_version\":1"));
104        assert!(json.contains("\"command\":\"summary\""));
105        assert!(json.contains("\"n\":7"));
106    }
107
108    #[test]
109    fn exit_codes_are_stable() {
110        assert_eq!(ExitCode::Clean as i32, 0);
111        assert_eq!(ExitCode::Findings as i32, 1);
112        assert_eq!(ExitCode::InvalidHar as i32, 2);
113        assert_eq!(ExitCode::UnsafeBlocked as i32, 3);
114    }
115}