1use chrono::DateTime;
2use std::str::FromStr;
3
4pub struct CriLog {
10 timestamp: DateTime<chrono::offset::FixedOffset>,
11 stream_type: StreamType,
12 tag: String,
13 log: String,
14}
15
16impl CriLog {
17 pub fn timestamp(&self) -> &DateTime<chrono::offset::FixedOffset> {
19 &self.timestamp
20 }
21
22 pub fn is_stderr(&self) -> bool {
24 self.stream_type == StreamType::StdErr
25 }
26
27 pub fn is_stdout(&self) -> bool {
29 self.stream_type == StreamType::StdOut
30 }
31
32 pub fn tag(&self) -> &str {
34 &self.tag
35 }
36
37 pub fn log(&self) -> &str {
39 &self.log
40 }
41}
42
43impl FromStr for CriLog {
44 type Err = ParsingError;
45
46 fn from_str(input: &str) -> Result<Self, <Self as FromStr>::Err> {
47 let mut iter = input.split_whitespace();
48
49 let timestamp_str = iter.next().ok_or(ParsingError::MissingTimestamp)?;
50 let timestamp = DateTime::parse_from_rfc3339(timestamp_str)
51 .map_err(|_| ParsingError::TimestampFormat(timestamp_str.into()))?;
52
53 let stream_type_str = iter.next().ok_or(ParsingError::MissingStreamType)?;
54 let stream_type = StreamType::from_str(stream_type_str)
55 .map_err(|_| ParsingError::InvalidStreamType(stream_type_str.into()))?;
56
57 let tag = iter.next().ok_or(ParsingError::MissingLogTag)?.to_owned();
58
59 let log = iter.collect::<Vec<&str>>().join(" ");
60
61 Ok(CriLog {
62 timestamp,
63 stream_type,
64 tag,
65 log,
66 })
67 }
68}
69
70#[derive(Debug, thiserror::Error)]
71pub enum ParsingError {
72 #[error("Missing timestamp in log entry")]
73 MissingTimestamp,
74 #[error("Timestamp format error: {0}")]
75 TimestampFormat(String),
76 #[error("Missing stream type")]
77 MissingStreamType,
78 #[error("Invalid stream type: {0}")]
79 InvalidStreamType(String),
80 #[error("Missing log tag")]
81 MissingLogTag,
82}
83
84#[derive(Debug, PartialEq)]
85pub enum StreamType {
86 StdOut,
87 StdErr,
88}
89
90impl FromStr for StreamType {
91 type Err = InvalidStreamType;
92 fn from_str(input: &str) -> Result<Self, <Self as FromStr>::Err> {
93 match input {
94 "stderr" => Ok(StreamType::StdErr),
95 "stdout" => Ok(StreamType::StdOut),
96 input => Err(InvalidStreamType(input.into())),
97 }
98 }
99}
100
101pub struct InvalidStreamType(String);
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 #[test]
107 fn stdout() {
108 let log_str = "2016-10-06T00:17:09.669794202Z stdout P log content 1";
111 let crilog = CriLog::from_str(log_str).expect("failed to parse");
112 assert!(crilog.is_stdout());
113 assert_eq!(crilog.tag(), "P");
114 assert_eq!(crilog.log(), "log content 1");
115 }
116
117 #[test]
118 fn stderr() {
119 let log_str = "2016-10-06T00:17:09.669794203Z stderr F log content 2";
120 let crilog = CriLog::from_str(log_str).expect("failed to parse");
121 assert!(crilog.is_stderr());
122 assert_eq!(crilog.tag(), "F");
123 assert_eq!(crilog.log(), "log content 2");
124 }
125}