Skip to main content

nexql_conn/
error.rs

1// SPDX-License-Identifier: GPL-3.0-only
2// Copyright (C) 2026 NexQL-OSS Team
3
4use thiserror::Error;
5
6use std::error::Error as StdError;
7
8#[derive(Debug, Error)]
9pub enum ConnError {
10    #[error("no connection source resolved")]
11    NoSource,
12
13    #[error("profile not found: {0}")]
14    ProfileNotFound(String),
15
16    #[error("invalid connection URL: {0}")]
17    InvalidUrl(String),
18
19    #[error("config error: {0}")]
20    Config(String),
21
22    #[error("password_command failed: {0}")]
23    PasswordCommand(String),
24
25    #[error("password_command produced empty stdout")]
26    EmptyPasswordCommand,
27
28    #[error("env-file error: {0}")]
29    EnvFile(String),
30
31    #[error("pgpass error: {0}")]
32    PgPass(String),
33
34    #[error("pool error: {0}")]
35    Pool(String),
36
37    #[error("postgres error: {}", format_postgres_error(.0))]
38    Postgres(#[from] tokio_postgres::Error),
39
40    #[error("io error: {0}")]
41    Io(#[from] std::io::Error),
42}
43
44/// Human-readable Postgres message (detail/hint included when present).
45pub fn format_postgres_error(e: &tokio_postgres::Error) -> String {
46    if let Some(db_err) = e.as_db_error() {
47        let mut msg = db_err.message().to_string();
48        if let Some(detail) = db_err.detail() {
49            msg.push_str(&format!(" ({detail})"));
50        }
51        if let Some(hint) = db_err.hint() {
52            msg.push_str(&format!(" [hint: {hint}]"));
53        }
54        msg
55    } else {
56        let mut msg = e.to_string();
57        if let Some(source) = StdError::source(e) {
58            let detail = source.to_string();
59            if !detail.is_empty() && detail != msg {
60                msg.push_str(&format!(" ({detail})"));
61            }
62        }
63        msg
64    }
65}