const FATAL_ERROR_PATTERNS: &[&str] = &[
"dial tcp:",
"Client.Timeout exceeded",
"connection refused",
"no such host",
"request canceled while waiting for connection",
"request canceled (Client.Timeout",
"tls: handshake",
"certificate",
"network is unreachable",
"connection reset by peer",
"broken pipe",
"EOF",
"http response status code: 401",
"http response status code: 403",
"could not locate symbol",
"cannot find matching operation",
"disparity in fields to insert",
"syntax error at position",
];
const NON_FATAL_OVERRIDES: &[&str] = &[
"http response status code: 404",
"ResourceNotFoundException",
"was not found",
];
pub fn check_fatal_error(error_msg: &str) -> Option<&'static str> {
for pattern in NON_FATAL_OVERRIDES {
if error_msg.contains(pattern) {
return None;
}
}
FATAL_ERROR_PATTERNS
.iter()
.find(|&&pattern| error_msg.contains(pattern))
.copied()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_timeout_is_fatal() {
let msg = r#"Query execution failed: query returns error: Post "https://cloudcontrolapi.us-east-1.amazonaws.com/?Action=GetResource&Version=2021-09-30": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)"#;
assert!(check_fatal_error(msg).is_some());
}
#[test]
fn test_dns_failure_is_fatal() {
let msg = r#"Query execution failed: query returns error: Post "https://cloudcontrolapi.us-east-1.amazonaws.com/": dial tcp: lookup cloudcontrolapi.us-east-1.amazonaws.com on 8.8.4.4:53: i/o timeout"#;
assert!(check_fatal_error(msg).is_some());
}
#[test]
fn test_403_is_fatal() {
let msg = r#"http response status code: 403, response body: {"message":"Access Denied"}"#;
assert!(check_fatal_error(msg).is_some());
}
#[test]
fn test_401_is_fatal() {
let msg = r#"http response status code: 401, response body: {"message":"Unauthorized"}"#;
assert!(check_fatal_error(msg).is_some());
}
#[test]
fn test_404_is_not_fatal() {
let msg = r#"http response status code: 404, response body: {"__type":"ResourceNotFoundException","Message":"Resource not found"}"#;
assert!(check_fatal_error(msg).is_none());
}
#[test]
fn test_resource_not_found_is_not_fatal() {
let msg = r#"Resource of type 'AWS::EC2::VPC' with identifier 'vpc-xxx' was not found"#;
assert!(check_fatal_error(msg).is_none());
}
#[test]
fn test_400_bad_request_is_not_fatal() {
let msg = r#"insert over HTTP error: 400 Bad Request"#;
assert!(check_fatal_error(msg).is_none());
}
#[test]
fn test_normal_query_error_is_not_fatal() {
let msg = r#"query returns error: no such column: foo"#;
assert!(check_fatal_error(msg).is_none());
}
#[test]
fn test_could_not_locate_symbol_is_fatal() {
let msg = r#"Query execution failed: could not locate symbol DBInstanceStatus"#;
assert!(check_fatal_error(msg).is_some());
}
#[test]
fn test_cannot_find_matching_operation_is_fatal() {
let msg = r#"cannot find matching operation, searched: [insert update delete]"#;
assert!(check_fatal_error(msg).is_some());
}
#[test]
fn test_syntax_error_is_fatal() {
let msg = r#"Query execution failed: syntax error at position 42 near 'form'"#;
assert!(check_fatal_error(msg).is_some());
}
#[test]
fn test_disparity_in_fields_is_fatal() {
let msg = r#"disparity in fields to insert and supplied data"#;
assert!(check_fatal_error(msg).is_some());
}
}