use std::error::Error;
use std::fmt;
use std::process::{Command, Output};
#[derive(Debug, Eq, PartialEq)]
pub enum ReadErrorDetail {
NonZeroExit {
exit_code: Option<i32>,
stderr: Option<String>,
},
CouldNotRunCommand,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ReadError {
pub reason: &'static str,
pub detail: ReadErrorDetail,
}
impl fmt::Display for ReadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.reason)
}
}
impl Error for ReadError {}
pub struct Reader;
impl Reader {
pub fn read() -> Result<String, ReadError> {
let output = Command::new("crontab").arg("-l").output();
match output {
Ok(output) => Self::handle_output_ok(&output),
Err(_) => Self::handle_output_err(),
}
}
fn handle_output_ok(output: &Output) -> Result<String, ReadError> {
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
} else {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
Err(ReadError {
reason: "Cannot read crontab of current user.",
detail: ReadErrorDetail::NonZeroExit {
exit_code: output.status.code(),
stderr: if stderr.is_empty() {
None
} else {
Some(stderr)
},
},
})
}
}
fn handle_output_err() -> Result<String, ReadError> {
Err(ReadError {
reason: "Unable to locate the crontab executable on the system.",
detail: ReadErrorDetail::CouldNotRunCommand,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::os::unix::process::ExitStatusExt;
use std::process::ExitStatus;
#[test]
fn readerror_format() {
let error = ReadError {
reason: "an error has occurred",
detail: ReadErrorDetail::CouldNotRunCommand,
};
assert_eq!(error.to_string(), "an error has occurred");
}
#[test]
fn successful_read() {
let output = Output {
status: ExitStatus::from_raw(0),
stdout: b"<stdout>".to_vec(),
stderr: b"<stderr>".to_vec(),
};
let res = Reader::handle_output_ok(&output).unwrap();
assert_eq!(res, "<stdout>");
}
#[test]
fn unsuccessful_read() {
let output = Output {
status: ExitStatus::from_raw(1),
stdout: b"<stdout>".to_vec(),
stderr: b"<stderr>".to_vec(),
};
let res = Reader::handle_output_ok(&output).unwrap_err();
assert_eq!(
res,
ReadError {
reason: "Cannot read crontab of current user.",
detail: ReadErrorDetail::NonZeroExit {
exit_code: None,
stderr: Some(String::from("<stderr>")),
},
}
);
}
#[test]
fn empty_stderr_string_gives_none() {
let output = Output {
status: ExitStatus::from_raw(1),
stdout: b"<stdout>".to_vec(),
stderr: b"".to_vec(), };
let res = Reader::handle_output_ok(&output).unwrap_err();
assert!(matches!(
res.detail,
ReadErrorDetail::NonZeroExit { stderr: None, .. }
));
}
#[test]
fn error_with_command() {
let res = Reader::handle_output_err().unwrap_err();
assert_eq!(
res,
ReadError {
reason: "Unable to locate the crontab executable on the system.",
detail: ReadErrorDetail::CouldNotRunCommand,
}
);
}
}