gh_workflow_parser/
errlog.rs

1//! Contains the ErrorLog struct describing a failed job log from GitHub Actions.
2use 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    // Failed job/step can be retrieved from a failed job log by looking at the prefix
12    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        // Now trim the prefix from the log
39        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    // yyyy-mm-dd
81    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 just --yes build-ci-image
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
117just --yes build-ci-image
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}