Skip to main content

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    /// Indicates a PATH entry used for worker discovery is not valid UTF-8.
41    WorkerBinaryPathNonUtf8,
42}
43
44/// Captures bootstrap-specific failures.
45#[derive(Debug, Error)]
46#[error("{report}")]
47pub struct BootstrapError {
48    kind: BootstrapErrorKind,
49    #[source]
50    report: Report,
51}
52
53impl BootstrapError {
54    /// Constructs a new bootstrap error with the provided kind and diagnostic
55    /// report.
56    #[must_use]
57    pub const fn new(kind: BootstrapErrorKind, report: Report) -> Self {
58        Self { kind, report }
59    }
60
61    /// Returns the semantic category for this bootstrap failure.
62    #[must_use]
63    pub const fn kind(&self) -> BootstrapErrorKind {
64        self.kind
65    }
66
67    /// Extracts the underlying diagnostic report.
68    pub fn into_report(self) -> Report {
69        self.report
70    }
71}
72
73impl From<Report> for BootstrapError {
74    fn from(report: Report) -> Self {
75        Self::new(BootstrapErrorKind::Other, report)
76    }
77}
78
79impl From<PrivilegeError> for BootstrapError {
80    fn from(err: PrivilegeError) -> Self {
81        let PrivilegeError(report) = err;
82        Self::new(BootstrapErrorKind::Other, report)
83    }
84}
85
86impl From<ConfigError> for BootstrapError {
87    fn from(err: ConfigError) -> Self {
88        let ConfigError(report) = err;
89        Self::new(BootstrapErrorKind::Other, report)
90    }
91}
92
93impl From<PgEmbeddedError> for BootstrapError {
94    fn from(err: PgEmbeddedError) -> Self {
95        match err {
96            PgEmbeddedError::Bootstrap(inner) => inner,
97            PgEmbeddedError::Privilege(inner) => inner.into(),
98            PgEmbeddedError::Config(inner) => inner.into(),
99        }
100    }
101}
102
103/// Captures privilege-management failures.
104#[derive(Debug, Error)]
105#[error(transparent)]
106pub struct PrivilegeError(#[from] Report);
107
108/// Captures configuration failures.
109#[derive(Debug, Error)]
110#[error(transparent)]
111pub struct ConfigError(#[from] Report);
112
113#[cfg(test)]
114mod tests {
115    //! Unit tests for error display formats.
116
117    use super::*;
118    use color_eyre::eyre::eyre;
119    use rstest::rstest;
120
121    #[rstest]
122    #[case::bootstrap(
123        "PG_EMBEDDED_WORKER must be set",
124        "bootstrap failed:",
125        |msg: &str| PgEmbeddedError::Bootstrap(BootstrapError::from(eyre!("{}", msg)))
126    )]
127    #[case::privilege(
128        "failed to drop privileges",
129        "privilege management failed:",
130        |msg: &str| PgEmbeddedError::Privilege(PrivilegeError::from(eyre!("{}", msg)))
131    )]
132    #[case::config(
133        "invalid port number",
134        "configuration parsing failed:",
135        |msg: &str| PgEmbeddedError::Config(ConfigError::from(eyre!("{}", msg)))
136    )]
137    fn pg_embedded_error_includes_inner_message(
138        #[case] inner_message: &str,
139        #[case] expected_prefix: &str,
140        #[case] constructor: fn(&str) -> PgEmbeddedError,
141    ) {
142        let pg_err = constructor(inner_message);
143
144        let display = pg_err.to_string();
145
146        assert!(
147            display.contains(expected_prefix),
148            "expected '{expected_prefix}' prefix, got: {display}"
149        );
150        assert!(
151            display.contains(inner_message),
152            "expected inner message '{inner_message}' in display, got: {display}"
153        );
154    }
155
156    #[test]
157    fn bootstrap_error_displays_report_message() {
158        let inner_message = "database connection failed";
159        let err = BootstrapError::from(eyre!(inner_message));
160
161        let display = err.to_string();
162
163        assert!(
164            display.contains(inner_message),
165            "expected '{inner_message}' in display, got: {display}"
166        );
167    }
168}