gh_workflow_parser/
errlog.rs1use once_cell::sync::Lazy;
3use std::error::Error;
4
5use regex::Regex;
6
7#[derive(Debug)]
8pub struct ErrorLog {
9 job_id: String,
10 no_prefix_log: String,
11 prefix: ErrLogPrefix,
13}
14
15impl ErrorLog {
16 pub fn new(job_id: String, raw_log: String) -> Result<Self, Box<dyn Error>> {
17 static PREFIX_RE: Lazy<Regex> = Lazy::new(|| {
18 Regex::new(r"^(?P<failed_job>.*)\t(?P<failed_step>.*)\t(?P<timestamp>[0-9]{4}-[0-9]{2}-[0-9]{2})T[0-9]{2}:[0-9]{2}:[0-9]{2}.*Z ")
19 .expect("Failed to compile regex")
20 });
21 let first_line = raw_log
22 .lines()
23 .next()
24 .expect("Expected raw log to have at least one line");
25 let caps = PREFIX_RE.captures(first_line).map_or_else(
26 || {
27 panic!(
28 "Expected the first line of the raw log to match the prefix regex: {first_line}"
29 )
30 },
31 |m| m,
32 );
33 let failed_job = caps.name("failed_job").unwrap().as_str().to_string();
34 let failed_step = caps.name("failed_step").unwrap().as_str().to_string();
35 let timestamp = caps.name("timestamp").unwrap().as_str().to_string();
36 let prefix = ErrLogPrefix::new(failed_job, failed_step, timestamp);
37
38 let no_prefix_log =
40 raw_log
41 .lines()
42 .fold(String::with_capacity(raw_log.len() / 2), |mut acc, line| {
43 let mut s = PREFIX_RE.replace(line, "").to_string();
44 s.push('\n');
45 acc.push_str(&s);
46 acc
47 });
48 Ok(Self {
49 job_id,
50 no_prefix_log,
51 prefix,
52 })
53 }
54
55 pub fn job_id(&self) -> &str {
56 &self.job_id
57 }
58
59 pub fn no_prefix_log(&self) -> &str {
60 &self.no_prefix_log
61 }
62
63 pub fn failed_job(&self) -> &str {
64 self.prefix.failed_job()
65 }
66
67 pub fn failed_step(&self) -> &str {
68 self.prefix.failed_step()
69 }
70
71 pub fn timestamp(&self) -> &str {
72 self.prefix.timestamp()
73 }
74}
75
76#[derive(Debug)]
77pub struct ErrLogPrefix {
78 failed_job: String,
79 failed_step: String,
80 timestamp: String,
82}
83
84impl ErrLogPrefix {
85 pub fn new(failed_job: String, failed_step: String, timestamp: String) -> Self {
86 Self {
87 failed_job,
88 failed_step,
89 timestamp,
90 }
91 }
92
93 pub fn failed_job(&self) -> &str {
94 &self.failed_job
95 }
96
97 pub fn failed_step(&self) -> &str {
98 &self.failed_step
99 }
100
101 pub fn timestamp(&self) -> &str {
102 &self.timestamp
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use pretty_assertions::assert_eq;
110
111 const TEST_LOG_STRING: &str = r#"Test template xilinx 📦 Build yocto image 2024-02-10T00:03:45.5797561Z ##[group]Run just --yes build-ci-image
112Test template xilinx 📦 Build yocto image 2024-02-10T00:03:45.5799911Z [36;1mjust --yes build-ci-image[0m
113Test template xilinx 📦 Build yocto image 2024-02-10T00:03:45.5843410Z shell: /usr/bin/bash -e {0}
114"#;
115
116 const TEST_LOG_STRING_NO_PREFIX: &str = r#"##[group]Run just --yes build-ci-image
117[36;1mjust --yes build-ci-image[0m
118shell: /usr/bin/bash -e {0}
119"#;
120
121 #[test]
122 fn test_errlog_prefix() {
123 let err_log = ErrorLog::new("123".to_string(), TEST_LOG_STRING.to_owned()).unwrap();
124 assert_eq!(err_log.failed_job(), "Test template xilinx");
125 assert_eq!(err_log.failed_step(), "📦 Build yocto image");
126 assert_eq!(err_log.timestamp(), "2024-02-10");
127
128 assert_eq!(err_log.no_prefix_log(), TEST_LOG_STRING_NO_PREFIX);
129 }
130}