1use std::path::PathBuf;
2
3pub struct Error {
5 inner: ErrorInner,
7}
8
9#[derive(Debug)]
10pub enum ErrorInner {
11 MakeTempDir(std::io::Error),
13
14 RunCommand(String, std::io::Error),
16
17 CommandFailed(String, std::process::Output),
19
20 SpawnServer(String, std::io::Error),
22
23 Create(PathBuf, std::io::Error),
25
26 Duplicate(PathBuf, std::io::Error),
28
29 Open(PathBuf, std::io::Error),
31
32 Read(PathBuf, std::io::Error),
34
35 ServerReadyTimeout,
37
38 KillServer(std::io::Error),
40
41 CleanDir(PathBuf, std::io::Error),
43
44 Connect(String, tokio_postgres::Error),
46}
47
48impl std::error::Error for Error {}
49
50impl std::fmt::Debug for Error {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 std::fmt::Debug::fmt(&self.inner, f)
53 }
54}
55
56impl std::fmt::Display for Error {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 std::fmt::Display::fmt(&self.inner, f)
59 }
60}
61
62impl std::fmt::Display for ErrorInner {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Self::MakeTempDir(e) => write!(f, "Failed to create temporary directory: {e}"),
66 Self::RunCommand(name, e) => write!(f, "Failed run command `{name}`: {e}"),
67 Self::CommandFailed(name, output) => report_exit_reason(f, name, output),
68 Self::SpawnServer(name, e) => write!(f, "Failed to run server command: {name}: {e}"),
69 Self::Create(path, e) => write!(f, "Failed to create {}: {e}", path.display()),
70 Self::Duplicate(path, e) => write!(f, "Failed to duplicate file descriptor of {}: {e}", path.display()),
71 Self::Open(path, e) => write!(f, "Failed to open {} for reading: {e}", path.display()),
72 Self::Read(path, e) => write!(f, "Failed to read from {}: {e}", path.display()),
73 Self::ServerReadyTimeout => write!(f, "Server did not become ready within the timeout"),
74 Self::KillServer(e) => write!(f, "Failed to terminate spanwed server: {e}"),
75 Self::CleanDir(path, e) => write!(f, "Failed to clean up temporary state directory {}: {e}", path.display()),
76 Self::Connect(address, e) => write!(f, "Failed to connect to server at {address}: {e}"),
77 }
78 }
79}
80
81fn report_exit_reason(f: &mut std::fmt::Formatter, name: &str, output: &std::process::Output) -> std::fmt::Result {
82 #[cfg(unix)]
83 {
84 use std::os::unix::process::ExitStatusExt;
85 if let Some(signal) = output.status.signal() {
86 return write!(f, "Command `{name}` was killed by signal {signal}");
87 }
88 }
89 if let Some(code) = output.status.code() {
90 write!(f, "Command `{name}` exitted with status {code}")?;
91 if let Ok(error) = std::str::from_utf8(&output.stderr) {
92 let error = error.trim();
93 if !error.is_empty() {
94 write!(f, "Error: \n{error}")?;
95 }
96 }
97 Ok(())
98 } else {
99 write!(f, "Command `{name}` failed with unknown termination reason")
100 }
101}
102
103impl From<ErrorInner> for Error {
104 fn from(inner: ErrorInner) -> Self {
105 Self { inner }
106 }
107}