use std::io;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, MigrationError>;
#[derive(Debug, Error)]
pub enum MigrationError {
#[error("invalid configuration: {0}")]
Config(String),
#[error("invalid connection string: {0}")]
InvalidConnectionString(String),
#[error("external command `{command}` failed: {message}")]
ExternalCommand {
command: String,
message: String,
},
#[error(
"required tool `{tool}` is not installed or not on $PATH: {reason}\n\
hint: install the matching PostgreSQL client tools (e.g. on Ubuntu \
`apt install postgresql-client-NN` where NN matches your source \
server's major version) and ensure they are on $PATH"
)]
MissingTool {
tool: String,
reason: String,
},
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("{}", format_pg_error(.0))]
Postgres(#[from] tokio_postgres::Error),
#[error("replication error: {0}")]
Replication(#[from] pg_walstream::ReplicationError),
#[error("apply error: {0}")]
Apply(String),
#[error("operation cancelled")]
Cancelled,
}
fn format_pg_error(err: &tokio_postgres::Error) -> String {
if let Some(db) = err.as_db_error() {
let mut msg = format!("postgres error: {}: {}", db.severity(), db.message());
if let Some(detail) = db.detail() {
msg.push_str("\nDETAIL: ");
msg.push_str(detail);
}
if let Some(hint) = db.hint() {
msg.push_str("\nHINT: ");
msg.push_str(hint);
}
msg
} else {
format!("postgres error: {err}")
}
}
impl MigrationError {
pub fn config(msg: impl Into<String>) -> Self {
Self::Config(msg.into())
}
pub fn external(command: impl Into<String>, message: impl Into<String>) -> Self {
Self::ExternalCommand {
command: command.into(),
message: message.into(),
}
}
pub fn apply(msg: impl Into<String>) -> Self {
Self::Apply(msg.into())
}
pub fn missing_tool(tool: impl Into<String>, reason: impl Into<String>) -> Self {
Self::MissingTool {
tool: tool.into(),
reason: reason.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_helper_constructs_variant() {
let err = MigrationError::config("bad");
assert!(matches!(err, MigrationError::Config(ref m) if m == "bad"));
assert_eq!(err.to_string(), "invalid configuration: bad");
}
#[test]
fn external_helper_formats_message() {
let err = MigrationError::external("pg_dump", "exit 1");
assert_eq!(err.to_string(), "external command `pg_dump` failed: exit 1");
}
#[test]
fn apply_helper_constructs_variant() {
let err = MigrationError::apply("oops");
assert!(matches!(err, MigrationError::Apply(ref m) if m == "oops"));
}
}