1use serde::Deserialize;
4use serde_json::Value;
5
6#[derive(Debug, Clone)]
10pub struct NetworkEntry {
11 pub frame_ref: Option<String>,
14 pub page_ref: Option<String>,
16 pub monotonic_time: Option<f64>,
19 pub started_date_time: String,
22 pub time: Option<f64>,
25 pub request: RequestSnapshot,
26 pub response: ResponseSnapshot,
27 pub raw_snapshot: Value,
31}
32
33#[derive(Debug, Clone)]
34pub struct RequestSnapshot {
35 pub method: String,
36 pub url: String,
37 pub http_version: String,
38 pub headers: Vec<HeaderEntry>,
39 pub headers_size: Option<u64>,
40 pub body_size: Option<u64>,
41 pub post_data: Option<RequestPostData>,
42}
43
44#[derive(Debug, Clone)]
45pub struct ResponseSnapshot {
46 pub status: Option<u16>,
47 pub status_text: String,
48 pub http_version: String,
49 pub headers: Vec<HeaderEntry>,
50 pub headers_size: Option<u64>,
51 pub body_size: Option<u64>,
52 pub redirect_url: Option<String>,
54 pub content: ResponseContent,
55}
56
57#[derive(Debug, Clone)]
58pub struct HeaderEntry {
59 pub name: String,
60 pub value: String,
61}
62
63#[derive(Debug, Clone)]
64pub struct RequestPostData {
65 pub file: String,
68}
69
70#[derive(Debug, Clone)]
71pub struct ResponseContent {
72 pub size: Option<u64>,
73 pub mime_type: String,
74 pub file: Option<String>,
78}
79
80#[derive(Deserialize)]
85#[serde(rename_all = "camelCase")]
86struct SnapshotWire {
87 #[serde(default, rename = "_frameref")]
88 frame_ref: Option<String>,
89 #[serde(default)]
90 pageref: Option<String>,
91 #[serde(default, rename = "_monotonicTime")]
92 monotonic_time: Option<f64>,
93 #[serde(default)]
94 started_date_time: String,
95 #[serde(default = "default_time")]
96 time: f64,
97 request: RequestWire,
98 response: ResponseWire,
99}
100
101fn default_time() -> f64 {
102 -1.0
103}
104
105#[derive(Deserialize)]
106#[serde(rename_all = "camelCase")]
107struct RequestWire {
108 method: String,
109 url: String,
110 #[serde(default)]
111 http_version: String,
112 #[serde(default)]
113 headers: Vec<HeaderEntryWire>,
114 #[serde(default = "default_neg_one")]
115 headers_size: i64,
116 #[serde(default = "default_neg_one")]
117 body_size: i64,
118 #[serde(default)]
119 post_data: Option<PostDataWire>,
120}
121
122#[derive(Deserialize)]
123#[serde(rename_all = "camelCase")]
124struct ResponseWire {
125 #[serde(default = "default_neg_one_i32")]
126 status: i32,
127 #[serde(default)]
128 status_text: String,
129 #[serde(default)]
130 http_version: String,
131 #[serde(default)]
132 headers: Vec<HeaderEntryWire>,
133 #[serde(default = "default_neg_one")]
134 headers_size: i64,
135 #[serde(default = "default_neg_one")]
136 body_size: i64,
137 #[serde(default)]
138 redirect_url: String,
139 content: ContentWire,
140}
141
142#[derive(Deserialize)]
143struct HeaderEntryWire {
144 name: String,
145 value: String,
146}
147
148#[derive(Deserialize)]
149struct PostDataWire {
150 #[serde(
152 rename = "_file",
153 alias = "_sha1",
154 deserialize_with = "crate::event::required_resource_path"
155 )]
156 file: String,
157}
158
159#[derive(Deserialize)]
160#[serde(rename_all = "camelCase")]
161struct ContentWire {
162 #[serde(default = "default_neg_one")]
163 size: i64,
164 #[serde(default)]
165 mime_type: String,
166 #[serde(
167 default,
168 rename = "_file",
169 alias = "_sha1",
170 deserialize_with = "crate::event::resource_path"
171 )]
172 file: Option<String>,
173}
174
175fn default_neg_one() -> i64 {
176 -1
177}
178fn default_neg_one_i32() -> i32 {
179 -1
180}
181
182fn unknown_neg_one_u64(n: i64) -> Option<u64> {
185 if n == -1 { None } else { Some(n as u64) }
186}
187
188fn unknown_neg_one_f64(n: f64) -> Option<f64> {
189 if n == -1.0 { None } else { Some(n) }
190}
191
192fn empty_string_to_none(s: String) -> Option<String> {
193 if s.is_empty() { None } else { Some(s) }
194}
195
196impl NetworkEntry {
197 pub(crate) fn from_snapshot(snapshot: Value) -> Result<Self, serde_json::Error> {
198 let wire = SnapshotWire::deserialize(&snapshot)?;
201 Ok(NetworkEntry {
202 frame_ref: wire.frame_ref,
203 page_ref: wire.pageref,
204 monotonic_time: wire.monotonic_time,
205 started_date_time: wire.started_date_time,
206 time: unknown_neg_one_f64(wire.time),
207 request: RequestSnapshot {
208 method: wire.request.method,
209 url: wire.request.url,
210 http_version: wire.request.http_version,
211 headers: wire
212 .request
213 .headers
214 .into_iter()
215 .map(|h| HeaderEntry {
216 name: h.name,
217 value: h.value,
218 })
219 .collect(),
220 headers_size: unknown_neg_one_u64(wire.request.headers_size),
221 body_size: unknown_neg_one_u64(wire.request.body_size),
222 post_data: wire
223 .request
224 .post_data
225 .map(|p| RequestPostData { file: p.file }),
226 },
227 response: ResponseSnapshot {
228 status: if wire.response.status == -1 {
229 None
230 } else {
231 Some(wire.response.status as u16)
232 },
233 status_text: wire.response.status_text,
234 http_version: wire.response.http_version,
235 headers: wire
236 .response
237 .headers
238 .into_iter()
239 .map(|h| HeaderEntry {
240 name: h.name,
241 value: h.value,
242 })
243 .collect(),
244 headers_size: unknown_neg_one_u64(wire.response.headers_size),
245 body_size: unknown_neg_one_u64(wire.response.body_size),
246 redirect_url: empty_string_to_none(wire.response.redirect_url),
247 content: ResponseContent {
248 size: unknown_neg_one_u64(wire.response.content.size),
249 mime_type: wire.response.content.mime_type,
250 file: wire.response.content.file,
251 },
252 },
253 raw_snapshot: snapshot,
254 })
255 }
256}