pub fn has_error_report(tail: &str) -> bool {
tail.lines()
.any(|line| line.starts_with("Error:") || has_error_name(line))
}
fn has_error_name(line: &str) -> bool {
line.match_indices("\"name\": \"").any(|(at, prefix)| {
let rest = &line[at + prefix.len()..];
let name: &str = &rest[..rest
.find(|c: char| !c.is_ascii_alphabetic())
.unwrap_or(rest.len())];
rest[name.len()..].starts_with('"') && name.ends_with("Error") && name.len() > "Error".len()
})
}
pub fn reported_skipped(tail: &str) -> bool {
tail.lines()
.any(|line| line.trim_start().starts_with("TASK SKIPPED"))
}
pub fn usage_limit_epoch(tail: &str) -> Option<u64> {
tail.split("usage limit reached|").skip(1).find_map(|rest| {
let digits = &rest[..rest
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(rest.len())];
(digits.len() >= 9).then(|| digits.parse().ok())?
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_line_at_start_only() {
assert!(has_error_report(
"some output\nError: unknown command\nmore"
));
assert!(!has_error_report("some output\n Error: indented\nmore"));
assert!(!has_error_report("an Error: mid-line mention"));
}
#[test]
fn error_name_json() {
assert!(has_error_report(r#"{"name": "AuthError", "data": {}}"#));
assert!(!has_error_report(r#"{"name": "Error"}"#)); assert!(!has_error_report(r#"{"name": "AuthErrorFoo"}"#)); assert!(!has_error_report(r#"{"name": "notanerror"}"#));
assert!(!has_error_report(r#"{"label": "AuthError"}"#));
}
#[test]
fn skip_marker() {
assert!(reported_skipped("nothing to do here\nTASK SKIPPED\n"));
assert!(reported_skipped("TASK SKIPPED — no open PRs\nmore output"));
assert!(!reported_skipped("the marker is TASK SKIPPED on its own line"));
assert!(!reported_skipped("TASK COMPLETED\n"));
}
#[test]
fn usage_limit() {
assert_eq!(
usage_limit_epoch("blah usage limit reached|1757000000 blah"),
Some(1757000000)
);
assert_eq!(usage_limit_epoch("usage limit reached|123"), None); assert_eq!(usage_limit_epoch("usage limit reached soon"), None);
assert_eq!(usage_limit_epoch("all quiet"), None);
}
}