cloudfront_logs/borrowed/
simple.rs

1use crate::{
2    borrowed::raw::{
3        Logline as RawLogline, UnvalidatedLogline as UnvalidatedRaw,
4        ValidatedLogline as ValidatedRaw,
5    },
6    shared::*,
7    types::*,
8};
9
10/// The validated simple log line
11///
12/// Most fields are parsed into more meaningful types.
13/// It only uses types available in the standard library.
14///
15/// If you need more specific types, use the [`typed`](crate::borrowed::typed) module.
16///
17/// On construction it checks if the line can be parsed.
18/// This is useful if you cannot skip the comment lines or have reason to not trust the input for format correctness.
19/// The latter should be only an issue if you do not use this crate on CloudFront logs directly.
20///
21/// # Panics
22///
23/// Construction can panic if the input is not a valid log line!
24///
25/// # Examples
26///
27/// Use `.try_from()` or `.try_into()` to construct an instance, since action can fail.
28///
29/// ```rust
30/// use cloudfront_logs::{borrowed::simple::ValidatedLogline, types::*};
31///
32/// let line = "2019-12-04	21:02:31	LAX1	392	192.0.2.100	GET	d111111abcdef8.cloudfront.net	/index.html	200	-	Mozilla/5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/78.0.3904.108%20Safari/537.36	-	-	Hit	SOX4xwn4XV6Q4rgb7XiVGOHms_BGlTAC4KyHmureZmBNrjGdRLiNIQ==	d111111abcdef8.cloudfront.net	https	23	0.001	-	TLSv1.2	ECDHE-RSA-AES128-GCM-SHA256	Hit	HTTP/2.0	-	-	11040	0.001	Hit	text/html	78	-	-";
33///
34/// let item = ValidatedLogline::try_from(line).unwrap();
35/// // alternative:
36/// let item: ValidatedLogline<'_> = line.try_into().unwrap();
37///
38/// assert_eq!(item.date, "2019-12-04");
39/// assert_eq!(item.sc_bytes, 392u64);
40/// assert_eq!(item.cs_protocol, CsProtocol::Https);
41/// ```
42pub type ValidatedLogline<'a> = Logline<'a, Validated>;
43
44/// The unvalidated simple log line
45///
46/// Most fields are parsed into more meaningful types.
47/// It only uses types available in the standard library.
48///
49/// If you need more specific types, use the [`typed`](crate::borrowed::typed) module.
50///
51/// Unlike [`ValidatedLogline`], this variant does not check if the line can be parsed.
52/// Use this if you already did a check before creating this struct.
53/// A common scenario is that you 1) trust the input data and 2) skipped the comment lines.
54///
55/// Note: This is the only variant which can use the `From` trait instead of `TryFrom`,
56/// because validation is skipped and the input data does not need to be parsed into other types.
57///
58/// # Panics
59///
60/// Construction can panic if the input is not a valid log line!
61///
62/// # Examples
63///
64/// Use `.try_from()` or `.try_into()` to construct an instance, since action can fail.
65///
66/// ```rust
67/// use cloudfront_logs::{borrowed::simple::UnvalidatedLogline, types::*};
68///
69/// let line = "2019-12-04	21:02:31	LAX1	392	192.0.2.100	GET	d111111abcdef8.cloudfront.net	/index.html	200	-	Mozilla/5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/78.0.3904.108%20Safari/537.36	-	-	Hit	SOX4xwn4XV6Q4rgb7XiVGOHms_BGlTAC4KyHmureZmBNrjGdRLiNIQ==	d111111abcdef8.cloudfront.net	https	23	0.001	-	TLSv1.2	ECDHE-RSA-AES128-GCM-SHA256	Hit	HTTP/2.0	-	-	11040	0.001	Hit	text/html	78	-	-";
70///
71/// let item = UnvalidatedLogline::try_from(line).unwrap();
72/// // alternative:
73/// let item: UnvalidatedLogline<'_> = line.try_into().unwrap();
74///
75/// assert_eq!(item.date, "2019-12-04");
76/// assert_eq!(item.sc_bytes, 392u64);
77/// assert_eq!(item.cs_protocol, CsProtocol::Https);
78/// ```
79pub type UnvalidatedLogline<'a> = Logline<'a, Unvalidated>;
80
81/// The generic, raw log line type
82///
83/// Do not use it directly, prefer [`ValidatedLogline`] or [`UnvalidatedLogline`] instead.
84#[must_use]
85#[derive(Debug, Clone, PartialEq)]
86pub struct Logline<'a, V> {
87    pub date: &'a str,
88    pub time: &'a str,
89    pub x_edge_location: &'a str,
90    pub sc_bytes: u64,
91    pub c_ip: IpAddr,
92    pub cs_method: &'a str,
93    pub cs_host: &'a str,
94    pub cs_uri_stem: &'a str,
95    pub sc_status: u16,
96    pub cs_referer: Option<&'a str>,
97    pub cs_user_agent: &'a str,
98    pub cs_uri_query: Option<&'a str>,
99    pub cs_cookie: Option<&'a str>,
100    pub x_edge_result_type: EdgeResultType,
101    pub x_edge_request_id: &'a str,
102    pub x_host_header: &'a str,
103    pub cs_protocol: CsProtocol,
104    pub cs_bytes: u64,
105    pub time_taken: Duration,
106    pub x_forwarded_for: Option<ForwardedForAddrs>,
107    pub ssl_protocol: Option<SslProtocol>,
108    pub ssl_cipher: Option<&'a str>,
109    pub x_edge_response_result_type: EdgeResultType,
110    pub cs_protocol_version: CsProtocolVersion,
111    pub fle_status: Option<&'a str>,
112    pub fle_encrypted_fields: Option<u64>,
113    pub c_port: u16,
114    pub time_to_first_byte: Duration,
115    pub x_edge_detailed_result_type: DetailedEdgeResultType,
116    pub sc_content_type: Option<&'a str>,
117    pub sc_content_len: Option<u64>,
118    pub sc_range_start: Option<i64>, // *1
119    pub sc_range_end: Option<i64>,   // *1
120    __marker: PhantomData<V>,
121}
122
123// *1: Range is signed only because `end` can sometimes be "-1";
124//     Probably some CloudFront oddity. 🤷🏻
125
126impl<'a> TryFrom<&'a str> for Logline<'a, Validated> {
127    type Error = &'static str;
128
129    fn try_from(line: &'a str) -> Result<Self, Self::Error> {
130        validate_line(line)?;
131        new_log_line(line)
132    }
133}
134
135impl<'a> TryFrom<&'a str> for Logline<'a, Unvalidated> {
136    type Error = &'static str;
137
138    fn try_from(line: &'a str) -> Result<Self, Self::Error> {
139        new_log_line(line)
140    }
141}
142
143fn new_log_line<V>(line: &str) -> Result<Logline<'_, V>, &'static str> {
144    let mut iter = MemchrTabSplitter::new(line);
145
146    let line = Logline {
147        date: iter.next().unwrap(),
148        time: iter.next().unwrap(),
149        x_edge_location: iter.next().unwrap(),
150        sc_bytes: iter
151            .next()
152            .unwrap()
153            .parse()
154            .map_err(|_e| "sc_bytes invalid")?,
155        c_ip: iter.next().unwrap().parse().map_err(|_e| "c_ip invalid")?,
156        cs_method: iter.next().unwrap(),
157        cs_host: iter.next().unwrap(),
158        cs_uri_stem: iter.next().unwrap(),
159        sc_status: iter
160            .next()
161            .unwrap()
162            .parse()
163            .map_err(|_e| "sc_status invalid")?,
164        cs_referer: iter.next().unwrap().as_optional_str(),
165        cs_user_agent: iter.next().unwrap(),
166        cs_uri_query: iter.next().unwrap().as_optional_str(),
167        cs_cookie: iter.next().unwrap().as_optional_str(),
168        x_edge_result_type: iter
169            .next()
170            .unwrap()
171            .parse()
172            .map_err(|_e| "x_edge_result_type invalid")?,
173        x_edge_request_id: iter.next().unwrap(),
174        x_host_header: iter.next().unwrap(),
175        cs_protocol: iter
176            .next()
177            .unwrap()
178            .parse()
179            .map_err(|_e| "cs_protocol invalid")?,
180        cs_bytes: iter
181            .next()
182            .unwrap()
183            .parse()
184            .map_err(|_e| "cs_bytes invalid")?,
185        time_taken: iter
186            .next()
187            .unwrap()
188            .parse::<f64>()
189            .map(Duration::from_secs_f64)
190            .map_err(|_e| "time_taken invalid")?,
191        x_forwarded_for: iter
192            .next()
193            .and_then(as_optional_t)
194            .transpose()
195            .map_err(|_e| "x_forwarded_for invalid")?,
196        ssl_protocol: iter
197            .next()
198            .and_then(as_optional_t)
199            .transpose()
200            .map_err(|_e| "ssl_protocol invalid")?,
201        ssl_cipher: iter.next().unwrap().as_optional_str(),
202        x_edge_response_result_type: iter
203            .next()
204            .unwrap()
205            .parse()
206            .map_err(|_e| "x_edge_response_result_type invalid")?,
207        cs_protocol_version: iter
208            .next()
209            .unwrap()
210            .parse()
211            .map_err(|_e| "cs_protocol_version invalid")?,
212        fle_status: iter.next().unwrap().as_optional_str(),
213        fle_encrypted_fields: iter
214            .next()
215            .and_then(as_optional_t)
216            .transpose()
217            .map_err(|_e| "fle_encrypted_fields invalid")?,
218        c_port: iter
219            .next()
220            .unwrap()
221            .parse()
222            .map_err(|_e| "c_port invalid")?,
223        time_to_first_byte: iter
224            .next()
225            .unwrap()
226            .parse::<f64>()
227            .map(Duration::from_secs_f64)
228            .map_err(|_e| "time_to_first_byte invalid")?,
229        x_edge_detailed_result_type: iter
230            .next()
231            .unwrap()
232            .parse()
233            .map_err(|_e| "x_edge_detailed_result_type invalid")?,
234        sc_content_type: iter.next().unwrap().as_optional_str(),
235        sc_content_len: iter
236            .next()
237            .and_then(as_optional_t)
238            .transpose()
239            .map_err(|_e| "sc_content_len invalid")?,
240        sc_range_start: iter
241            .next()
242            .and_then(as_optional_t)
243            .transpose()
244            .map_err(|_e| "sc_range_start invalid")?,
245        sc_range_end: iter
246            .next()
247            .and_then(as_optional_t)
248            .transpose()
249            .map_err(|_e| "sc_range_end invalid")?,
250        __marker: PhantomData,
251    };
252    Ok(line)
253}
254
255impl<'a> TryFrom<ValidatedRaw<'a>> for Logline<'a, Validated> {
256    type Error = &'static str;
257
258    fn try_from(raw: ValidatedRaw<'a>) -> Result<Self, Self::Error> {
259        try_from_v(raw)
260    }
261}
262
263impl<'a> TryFrom<UnvalidatedRaw<'a>> for Logline<'a, Unvalidated> {
264    type Error = &'static str;
265
266    fn try_from(raw: UnvalidatedRaw<'a>) -> Result<Self, Self::Error> {
267        try_from_v(raw)
268    }
269}
270
271fn try_from_v<V>(raw: RawLogline<'_, V>) -> Result<Logline<'_, V>, &'static str> {
272    let line = Logline {
273        date: raw.date,
274        time: raw.time,
275        x_edge_location: raw.x_edge_location,
276        sc_bytes: raw.sc_bytes.parse().map_err(|_e| "sc_bytes invalid")?,
277        c_ip: raw.c_ip.parse().map_err(|_e| "c_ip invalid")?,
278        cs_method: raw.cs_method,
279        cs_host: raw.cs_host,
280        cs_uri_stem: raw.cs_uri_stem,
281        sc_status: raw.sc_status.parse().map_err(|_e| "sc_status invalid")?,
282        cs_referer: raw.cs_referer.as_optional_str(),
283        cs_user_agent: raw.cs_user_agent,
284        cs_uri_query: raw.cs_uri_query.as_optional_str(),
285        cs_cookie: raw.cs_cookie.as_optional_str(),
286        x_edge_result_type: raw
287            .x_edge_result_type
288            .parse()
289            .map_err(|_e| "x_edge_result_type invalid")?,
290        x_edge_request_id: raw.x_edge_request_id,
291        x_host_header: raw.x_host_header,
292        cs_protocol: raw
293            .cs_protocol
294            .parse()
295            .map_err(|_e| "cs_protocol invalid")?,
296        cs_bytes: raw.cs_bytes.parse().map_err(|_e| "cs_bytes invalid")?,
297        time_taken: raw
298            .time_taken
299            .parse::<f64>()
300            .map(Duration::from_secs_f64)
301            .map_err(|_e| "time_taken invalid")?,
302        x_forwarded_for: parse_as_option(raw.x_forwarded_for)
303            .map_err(|_e| "x_forwarded_for invalid")?,
304        ssl_protocol: parse_as_option(raw.ssl_protocol).map_err(|_e| "ssl_protocol invalid")?,
305        ssl_cipher: raw.ssl_cipher.as_optional_str(),
306        x_edge_response_result_type: raw
307            .x_edge_response_result_type
308            .parse()
309            .map_err(|_e| "x_edge_response_result_type invalid")?,
310        cs_protocol_version: raw
311            .cs_protocol_version
312            .parse()
313            .map_err(|_e| "cs_protocol_version invalid")?,
314        fle_status: raw.fle_status.as_optional_str(),
315        fle_encrypted_fields: parse_as_option(raw.fle_encrypted_fields)
316            .map_err(|_e| "fle_encrypted_fields invalid")?,
317        c_port: raw.c_port.parse().map_err(|_e| "c_port invalid")?,
318        time_to_first_byte: raw
319            .time_to_first_byte
320            .parse::<f64>()
321            .map(Duration::from_secs_f64)
322            .map_err(|_e| "time_to_first_byte invalid")?,
323        x_edge_detailed_result_type: raw
324            .x_edge_detailed_result_type
325            .parse()
326            .map_err(|_e| "x_edge_detailed_result_type invalid")?,
327        sc_content_type: raw.sc_content_type.as_optional_str(),
328        sc_content_len: parse_as_option(raw.sc_content_len)
329            .map_err(|_e| "sc_content_len invalid")?,
330        sc_range_start: parse_as_option(raw.sc_range_start)
331            .map_err(|_e| "sc_range_start invalid")?,
332        sc_range_end: parse_as_option(raw.sc_range_end).map_err(|_e| "sc_range_end invalid")?,
333        __marker: PhantomData,
334    };
335    Ok(line)
336}
337
338impl<'a> From<Logline<'a, Validated>> for Logline<'a, Unvalidated> {
339    fn from(validated: Logline<'a, Validated>) -> Self {
340        Logline {
341            date: validated.date,
342            time: validated.time,
343            x_edge_location: validated.x_edge_location,
344            sc_bytes: validated.sc_bytes,
345            c_ip: validated.c_ip,
346            cs_method: validated.cs_method,
347            cs_host: validated.cs_host,
348            cs_uri_stem: validated.cs_uri_stem,
349            sc_status: validated.sc_status,
350            cs_referer: validated.cs_referer,
351            cs_user_agent: validated.cs_user_agent,
352            cs_uri_query: validated.cs_uri_query,
353            cs_cookie: validated.cs_cookie,
354            x_edge_result_type: validated.x_edge_result_type,
355            x_edge_request_id: validated.x_edge_request_id,
356            x_host_header: validated.x_host_header,
357            cs_protocol: validated.cs_protocol,
358            cs_bytes: validated.cs_bytes,
359            time_taken: validated.time_taken,
360            x_forwarded_for: validated.x_forwarded_for,
361            ssl_protocol: validated.ssl_protocol,
362            ssl_cipher: validated.ssl_cipher,
363            x_edge_response_result_type: validated.x_edge_response_result_type,
364            cs_protocol_version: validated.cs_protocol_version,
365            fle_status: validated.fle_status,
366            fle_encrypted_fields: validated.fle_encrypted_fields,
367            c_port: validated.c_port,
368            time_to_first_byte: validated.time_to_first_byte,
369            x_edge_detailed_result_type: validated.x_edge_detailed_result_type,
370            sc_content_type: validated.sc_content_type,
371            sc_content_len: validated.sc_content_len,
372            sc_range_start: validated.sc_range_start,
373            sc_range_end: validated.sc_range_end,
374            __marker: PhantomData,
375        }
376    }
377}
378
379impl<'a> From<Logline<'a, Unvalidated>> for Logline<'a, Validated> {
380    fn from(unvalidated: Logline<'a, Unvalidated>) -> Self {
381        Logline {
382            date: unvalidated.date,
383            time: unvalidated.time,
384            x_edge_location: unvalidated.x_edge_location,
385            sc_bytes: unvalidated.sc_bytes,
386            c_ip: unvalidated.c_ip,
387            cs_method: unvalidated.cs_method,
388            cs_host: unvalidated.cs_host,
389            cs_uri_stem: unvalidated.cs_uri_stem,
390            sc_status: unvalidated.sc_status,
391            cs_referer: unvalidated.cs_referer,
392            cs_user_agent: unvalidated.cs_user_agent,
393            cs_uri_query: unvalidated.cs_uri_query,
394            cs_cookie: unvalidated.cs_cookie,
395            x_edge_result_type: unvalidated.x_edge_result_type,
396            x_edge_request_id: unvalidated.x_edge_request_id,
397            x_host_header: unvalidated.x_host_header,
398            cs_protocol: unvalidated.cs_protocol,
399            cs_bytes: unvalidated.cs_bytes,
400            time_taken: unvalidated.time_taken,
401            x_forwarded_for: unvalidated.x_forwarded_for,
402            ssl_protocol: unvalidated.ssl_protocol,
403            ssl_cipher: unvalidated.ssl_cipher,
404            x_edge_response_result_type: unvalidated.x_edge_response_result_type,
405            cs_protocol_version: unvalidated.cs_protocol_version,
406            fle_status: unvalidated.fle_status,
407            fle_encrypted_fields: unvalidated.fle_encrypted_fields,
408            c_port: unvalidated.c_port,
409            time_to_first_byte: unvalidated.time_to_first_byte,
410            x_edge_detailed_result_type: unvalidated.x_edge_detailed_result_type,
411            sc_content_type: unvalidated.sc_content_type,
412            sc_content_len: unvalidated.sc_content_len,
413            sc_range_start: unvalidated.sc_range_start,
414            sc_range_end: unvalidated.sc_range_end,
415            __marker: PhantomData,
416        }
417    }
418}