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
46pub 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
65pub 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}