use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum WireError {
#[error("connection error: {0}")]
Connection(String),
#[error("authentication failed: {0}")]
Authentication(String),
#[error("protocol error: {0}")]
Protocol(String),
#[error("sql error: {0}")]
Sql(String),
#[error("json decode error: {0}")]
JsonDecode(#[from] serde_json::Error),
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("invalid configuration: {0}")]
Config(String),
#[error("query cancelled")]
Cancelled,
#[error("invalid result schema: {0}")]
InvalidSchema(String),
#[error("connection busy: {0}")]
ConnectionBusy(String),
#[error("invalid connection state: expected {expected}, got {actual}")]
InvalidState {
expected: String,
actual: String,
},
#[error("connection closed")]
ConnectionClosed,
#[error("deserialization error for type '{type_name}': {details}")]
Deserialization {
type_name: String,
details: String,
},
#[error("memory limit exceeded: {estimated_memory} bytes buffered > {limit} bytes limit")]
MemoryLimitExceeded {
limit: usize,
estimated_memory: usize,
},
}
pub type Result<T> = std::result::Result<T, WireError>;
impl WireError {
pub fn connection<S: Into<String>>(msg: S) -> Self {
WireError::Connection(msg.into())
}
#[must_use]
pub fn connection_refused(host: &str, port: u16) -> Self {
WireError::Connection(format!(
"failed to connect to {}:{}: connection refused. \
Is Postgres running? Verify with: pg_isready -h {} -p {}",
host, port, host, port
))
}
pub fn protocol<S: Into<String>>(msg: S) -> Self {
WireError::Protocol(msg.into())
}
pub fn sql<S: Into<String>>(msg: S) -> Self {
WireError::Sql(msg.into())
}
#[must_use]
pub fn invalid_schema_columns(num_columns: usize) -> Self {
WireError::InvalidSchema(format!(
"query returned {} columns instead of 1. \
fraiseql-wire supports only: SELECT data FROM <view>. \
See troubleshooting.md#error-invalid-result-schema",
num_columns
))
}
pub fn invalid_schema<S: Into<String>>(msg: S) -> Self {
WireError::InvalidSchema(msg.into())
}
#[must_use]
pub fn auth_failed(username: &str, reason: &str) -> Self {
WireError::Authentication(format!(
"authentication failed for user '{}': {}. \
Verify credentials with: psql -U {} -W",
username, reason, username
))
}
pub fn config_invalid<S: Into<String>>(msg: S) -> Self {
WireError::Config(format!(
"invalid configuration: {}. \
Expected format: postgres://[user[:password]@][host[:port]]/[database]",
msg.into()
))
}
#[must_use]
pub const fn is_retriable(&self) -> bool {
matches!(self, WireError::Io(_) | WireError::ConnectionClosed)
}
#[must_use]
pub const fn category(&self) -> &'static str {
match self {
WireError::Connection(_) => "connection",
WireError::Authentication(_) => "authentication",
WireError::Protocol(_) => "protocol",
WireError::Sql(_) => "sql",
WireError::JsonDecode(_) => "json_decode",
WireError::Io(_) => "io",
WireError::Config(_) => "config",
WireError::Cancelled => "cancelled",
WireError::InvalidSchema(_) => "invalid_schema",
WireError::ConnectionBusy(_) => "connection_busy",
WireError::InvalidState { .. } => "invalid_state",
WireError::ConnectionClosed => "connection_closed",
WireError::Deserialization { .. } => "deserialization",
WireError::MemoryLimitExceeded { .. } => "memory_limit_exceeded",
}
}
}
#[cfg(test)]
mod tests;