1use crate::classify::ResourceType;
2use serde::Serialize;
3
4pub fn format_entry_id(index: usize) -> String {
6 format!("e{index:06}")
7}
8
9#[derive(Debug, Clone, Serialize)]
10pub struct CaptureMeta {
11 pub har_version: String,
12 pub creator: String,
13 pub creator_version: String,
14 pub browser: Option<String>,
15 pub entry_count: usize,
16 pub start_ms: Option<i64>,
17 pub end_ms: Option<i64>,
18 pub duration_ms: f64,
19}
20
21#[derive(Debug, Clone)]
22pub struct Capture {
23 pub meta: CaptureMeta,
24 pub entries: Vec<Entry>,
25}
26
27#[derive(Debug, Clone)]
28pub struct Entry {
29 pub id: String,
30 pub index: usize,
31 pub started_offset_ms: f64,
32 pub duration_ms: f64,
33 pub method: String,
34 pub url: String,
35 pub host: String,
36 pub path: String,
37 pub norm_path: String,
38 pub query: Vec<(String, String)>,
39 pub status: i64,
40 pub status_text: String,
41 pub resource_type: ResourceType,
42 pub content_type: Option<String>,
43 pub req_headers: Vec<(String, String)>,
44 pub resp_headers: Vec<(String, String)>,
45 pub req_body: Option<String>,
46 pub resp_body: Option<String>,
47 pub timings: Phases,
48 pub sizes: Sizes,
49 pub server_ip: Option<String>,
50 pub http_version: String,
51 pub redirect_url: Option<String>,
52 pub correlation: Vec<(String, String)>,
53}
54
55#[derive(Debug, Clone, Default)]
56pub struct Phases {
57 pub blocked: Option<f64>,
58 pub dns: Option<f64>,
59 pub connect: Option<f64>,
60 pub ssl: Option<f64>,
61 pub send: f64,
62 pub wait: f64,
63 pub receive: f64,
64}
65
66#[derive(Debug, Clone, Default)]
67pub struct Sizes {
68 pub req_body: i64,
69 pub resp_body: i64,
70 pub resp_content: i64,
71 pub resp_headers: i64,
72}
73
74impl Entry {
75 pub fn status_class(&self) -> i64 {
77 if (100..600).contains(&self.status) {
78 self.status / 100
79 } else {
80 0
81 }
82 }
83
84 pub fn is_error(&self) -> bool {
85 self.status_class() == 4 || self.status_class() == 5 || self.status == 0
86 }
87}
88
89#[cfg(test)]
90pub fn sample_entry(index: usize, host: &str, method: &str, path: &str, status: i64) -> Entry {
91 Entry {
92 id: format_entry_id(index),
93 index,
94 started_offset_ms: index as f64 * 10.0,
95 duration_ms: 10.0,
96 method: method.to_string(),
97 url: format!("https://{host}{path}"),
98 host: host.to_string(),
99 path: path.to_string(),
100 norm_path: path.to_string(),
101 query: Vec::new(),
102 status,
103 status_text: String::new(),
104 resource_type: ResourceType::Api,
105 content_type: Some("application/json".to_string()),
106 req_headers: Vec::new(),
107 resp_headers: Vec::new(),
108 req_body: None,
109 resp_body: None,
110 timings: Phases::default(),
111 sizes: Sizes::default(),
112 server_ip: None,
113 http_version: "HTTP/2".to_string(),
114 redirect_url: None,
115 correlation: Vec::new(),
116 }
117}
118
119#[cfg(test)]
120pub fn sample_capture(entries: Vec<Entry>) -> Capture {
121 let meta = CaptureMeta {
122 har_version: "1.2".to_string(),
123 creator: "test".to_string(),
124 creator_version: "0".to_string(),
125 browser: None,
126 entry_count: entries.len(),
127 start_ms: Some(0),
128 end_ms: Some(0),
129 duration_ms: 0.0,
130 };
131 Capture { meta, entries }
132}