use std::path::PathBuf;
pub struct Error {
inner: ErrorInner,
}
#[derive(Debug)]
pub enum ErrorInner {
MakeTempDir(std::io::Error),
RunCommand(String, std::io::Error),
CommandFailed(String, std::process::Output),
SpawnServer(String, std::io::Error),
Create(PathBuf, std::io::Error),
Duplicate(PathBuf, std::io::Error),
Open(PathBuf, std::io::Error),
Read(PathBuf, std::io::Error),
ServerReadyTimeout,
KillServer(std::io::Error),
CleanDir(PathBuf, std::io::Error),
Connect(String, tokio_postgres::Error),
}
impl std::error::Error for Error {}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.inner, f)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.inner, f)
}
}
impl std::fmt::Display for ErrorInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MakeTempDir(e) => write!(f, "Failed to create temporary directory: {e}"),
Self::RunCommand(name, e) => write!(f, "Failed run command `{name}`: {e}"),
Self::CommandFailed(name, output) => report_exit_reason(f, name, output),
Self::SpawnServer(name, e) => write!(f, "Failed to run server command: {name}: {e}"),
Self::Create(path, e) => write!(f, "Failed to create {}: {e}", path.display()),
Self::Duplicate(path, e) => write!(f, "Failed to duplicate file descriptor of {}: {e}", path.display()),
Self::Open(path, e) => write!(f, "Failed to open {} for reading: {e}", path.display()),
Self::Read(path, e) => write!(f, "Failed to read from {}: {e}", path.display()),
Self::ServerReadyTimeout => write!(f, "Server did not become ready within the timeout"),
Self::KillServer(e) => write!(f, "Failed to terminate spanwed server: {e}"),
Self::CleanDir(path, e) => write!(f, "Failed to clean up temporary state directory {}: {e}", path.display()),
Self::Connect(address, e) => write!(f, "Failed to connect to server at {address}: {e}"),
}
}
}
fn report_exit_reason(f: &mut std::fmt::Formatter, name: &str, output: &std::process::Output) -> std::fmt::Result {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = output.status.signal() {
return write!(f, "Command `{name}` was killed by signal {signal}");
}
}
if let Some(code) = output.status.code() {
write!(f, "Command `{name}` exitted with status {code}")?;
if let Ok(error) = std::str::from_utf8(&output.stderr) {
let error = error.trim();
if !error.is_empty() {
write!(f, "Error: \n{error}")?;
}
}
Ok(())
} else {
write!(f, "Command `{name}` failed with unknown termination reason")
}
}
impl From<ErrorInner> for Error {
fn from(inner: ErrorInner) -> Self {
Self { inner }
}
}