pg_embedded_setup_unpriv/
error.rs

1//! Domain error types for the embedded `PostgreSQL` bootstrapper.
2
3use color_eyre::Report;
4use thiserror::Error;
5
6/// Result alias for operations that may return a [`PgEmbeddedError`].
7pub type Result<T> = std::result::Result<T, PgEmbeddedError>;
8
9/// Result alias for bootstrap-specific fallible operations.
10pub type BootstrapResult<T> = std::result::Result<T, BootstrapError>;
11
12/// Result alias for privilege-management fallible operations.
13pub type PrivilegeResult<T> = std::result::Result<T, PrivilegeError>;
14
15/// Result alias for configuration fallible operations.
16pub type ConfigResult<T> = std::result::Result<T, ConfigError>;
17
18/// Top-level error exposed by the crate.
19#[derive(Debug, Error)]
20pub enum PgEmbeddedError {
21    /// Indicates bootstrap initialisation failed.
22    #[error("bootstrap failed: {0}")]
23    Bootstrap(#[from] BootstrapError),
24    /// Indicates privilege management failed.
25    #[error("privilege management failed: {0}")]
26    Privilege(#[from] PrivilegeError),
27    /// Indicates configuration parsing failed.
28    #[error("configuration parsing failed: {0}")]
29    Config(#[from] ConfigError),
30}
31
32/// Categorises bootstrap failures so callers can branch on structured errors.
33#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
34pub enum BootstrapErrorKind {
35    /// Represents errors without a more specific semantic meaning.
36    #[default]
37    Other,
38    /// Indicates the configured worker binary is missing from disk.
39    WorkerBinaryMissing,
40}
41
42/// Captures bootstrap-specific failures.
43#[derive(Debug, Error)]
44#[error("{report}")]
45pub struct BootstrapError {
46    kind: BootstrapErrorKind,
47    #[source]
48    report: Report,
49}
50
51impl BootstrapError {
52    /// Constructs a new bootstrap error with the provided kind and diagnostic
53    /// report.
54    #[must_use]
55    pub const fn new(kind: BootstrapErrorKind, report: Report) -> Self {
56        Self { kind, report }
57    }
58
59    /// Returns the semantic category for this bootstrap failure.
60    #[must_use]
61    pub const fn kind(&self) -> BootstrapErrorKind {
62        self.kind
63    }
64
65    /// Extracts the underlying diagnostic report.
66    pub fn into_report(self) -> Report {
67        self.report
68    }
69}
70
71impl From<Report> for BootstrapError {
72    fn from(report: Report) -> Self {
73        Self::new(BootstrapErrorKind::Other, report)
74    }
75}
76
77impl From<PrivilegeError> for BootstrapError {
78    fn from(err: PrivilegeError) -> Self {
79        let PrivilegeError(report) = err;
80        Self::new(BootstrapErrorKind::Other, report)
81    }
82}
83
84impl From<ConfigError> for BootstrapError {
85    fn from(err: ConfigError) -> Self {
86        let ConfigError(report) = err;
87        Self::new(BootstrapErrorKind::Other, report)
88    }
89}
90
91impl From<PgEmbeddedError> for BootstrapError {
92    fn from(err: PgEmbeddedError) -> Self {
93        match err {
94            PgEmbeddedError::Bootstrap(inner) => inner,
95            PgEmbeddedError::Privilege(inner) => inner.into(),
96            PgEmbeddedError::Config(inner) => inner.into(),
97        }
98    }
99}
100
101/// Captures privilege-management failures.
102#[derive(Debug, Error)]
103#[error(transparent)]
104pub struct PrivilegeError(#[from] Report);
105
106/// Captures configuration failures.
107#[derive(Debug, Error)]
108#[error(transparent)]
109pub struct ConfigError(#[from] Report);
110
111#[cfg(test)]
112mod tests {
113    //! Unit tests for error display formats.
114
115    use super::*;
116    use color_eyre::eyre::eyre;
117    use rstest::rstest;
118
119    #[rstest]
120    #[case::bootstrap(
121        "PG_EMBEDDED_WORKER must be set",
122        "bootstrap failed:",
123        |msg: &str| PgEmbeddedError::Bootstrap(BootstrapError::from(eyre!("{}", msg)))
124    )]
125    #[case::privilege(
126        "failed to drop privileges",
127        "privilege management failed:",
128        |msg: &str| PgEmbeddedError::Privilege(PrivilegeError::from(eyre!("{}", msg)))
129    )]
130    #[case::config(
131        "invalid port number",
132        "configuration parsing failed:",
133        |msg: &str| PgEmbeddedError::Config(ConfigError::from(eyre!("{}", msg)))
134    )]
135    fn pg_embedded_error_includes_inner_message(
136        #[case] inner_message: &str,
137        #[case] expected_prefix: &str,
138        #[case] constructor: fn(&str) -> PgEmbeddedError,
139    ) {
140        let pg_err = constructor(inner_message);
141
142        let display = pg_err.to_string();
143
144        assert!(
145            display.contains(expected_prefix),
146            "expected '{expected_prefix}' prefix, got: {display}"
147        );
148        assert!(
149            display.contains(inner_message),
150            "expected inner message '{inner_message}' in display, got: {display}"
151        );
152    }
153
154    #[test]
155    fn bootstrap_error_displays_report_message() {
156        let inner_message = "database connection failed";
157        let err = BootstrapError::from(eyre!(inner_message));
158
159        let display = err.to_string();
160
161        assert!(
162            display.contains(inner_message),
163            "expected '{inner_message}' in display, got: {display}"
164        );
165    }
166}