pg_embedded_setup_unpriv/
error.rs1use color_eyre::Report;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, PgEmbeddedError>;
8
9pub type BootstrapResult<T> = std::result::Result<T, BootstrapError>;
11
12pub type PrivilegeResult<T> = std::result::Result<T, PrivilegeError>;
14
15pub type ConfigResult<T> = std::result::Result<T, ConfigError>;
17
18#[derive(Debug, Error)]
20pub enum PgEmbeddedError {
21 #[error("bootstrap failed: {0}")]
23 Bootstrap(#[from] BootstrapError),
24 #[error("privilege management failed: {0}")]
26 Privilege(#[from] PrivilegeError),
27 #[error("configuration parsing failed: {0}")]
29 Config(#[from] ConfigError),
30}
31
32#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
34pub enum BootstrapErrorKind {
35 #[default]
37 Other,
38 WorkerBinaryMissing,
40 WorkerBinaryPathNonUtf8,
42}
43
44#[derive(Debug, Error)]
46#[error("{report}")]
47pub struct BootstrapError {
48 kind: BootstrapErrorKind,
49 #[source]
50 report: Report,
51}
52
53impl BootstrapError {
54 #[must_use]
57 pub const fn new(kind: BootstrapErrorKind, report: Report) -> Self { Self { kind, report } }
58
59 #[must_use]
61 pub const fn kind(&self) -> BootstrapErrorKind { self.kind }
62
63 pub fn into_report(self) -> Report { self.report }
65}
66
67impl From<Report> for BootstrapError {
68 fn from(report: Report) -> Self { Self::new(BootstrapErrorKind::Other, report) }
69}
70
71impl From<PrivilegeError> for BootstrapError {
72 fn from(err: PrivilegeError) -> Self {
73 let PrivilegeError(report) = err;
74 Self::new(BootstrapErrorKind::Other, report)
75 }
76}
77
78impl From<ConfigError> for BootstrapError {
79 fn from(err: ConfigError) -> Self {
80 let ConfigError(report) = err;
81 Self::new(BootstrapErrorKind::Other, report)
82 }
83}
84
85impl From<PgEmbeddedError> for BootstrapError {
86 fn from(err: PgEmbeddedError) -> Self {
87 match err {
88 PgEmbeddedError::Bootstrap(inner) => inner,
89 PgEmbeddedError::Privilege(inner) => inner.into(),
90 PgEmbeddedError::Config(inner) => inner.into(),
91 }
92 }
93}
94
95#[derive(Debug, Error)]
97#[error(transparent)]
98pub struct PrivilegeError(#[from] Report);
99
100#[derive(Debug, Error)]
102#[error(transparent)]
103pub struct ConfigError(#[from] Report);
104
105#[cfg(test)]
106mod tests {
107 use color_eyre::eyre::eyre;
110 use rstest::rstest;
111
112 use super::*;
113
114 #[rstest]
115 #[case::bootstrap(
116 "PG_EMBEDDED_WORKER must be set",
117 "bootstrap failed:",
118 |msg: &str| PgEmbeddedError::Bootstrap(BootstrapError::from(eyre!("{}", msg)))
119 )]
120 #[case::privilege(
121 "failed to drop privileges",
122 "privilege management failed:",
123 |msg: &str| PgEmbeddedError::Privilege(PrivilegeError::from(eyre!("{}", msg)))
124 )]
125 #[case::config(
126 "invalid port number",
127 "configuration parsing failed:",
128 |msg: &str| PgEmbeddedError::Config(ConfigError::from(eyre!("{}", msg)))
129 )]
130 fn pg_embedded_error_includes_inner_message(
131 #[case] inner_message: &str,
132 #[case] expected_prefix: &str,
133 #[case] constructor: fn(&str) -> PgEmbeddedError,
134 ) {
135 let pg_err = constructor(inner_message);
136
137 let display = pg_err.to_string();
138
139 assert!(
140 display.contains(expected_prefix),
141 "expected '{expected_prefix}' prefix, got: {display}"
142 );
143 assert!(
144 display.contains(inner_message),
145 "expected inner message '{inner_message}' in display, got: {display}"
146 );
147 }
148
149 #[test]
150 fn bootstrap_error_displays_report_message() {
151 let inner_message = "database connection failed";
152 let err = BootstrapError::from(eyre!(inner_message));
153
154 let display = err.to_string();
155
156 assert!(
157 display.contains(inner_message),
158 "expected '{inner_message}' in display, got: {display}"
159 );
160 }
161
162 #[test]
163 fn bootstrap_error_kind_defaults_to_other() {
164 assert_eq!(BootstrapErrorKind::default(), BootstrapErrorKind::Other);
165 }
166
167 #[test]
168 fn bootstrap_error_preserves_kind_and_into_report() {
169 let err = BootstrapError::new(
170 BootstrapErrorKind::WorkerBinaryMissing,
171 eyre!("worker gone"),
172 );
173 assert_eq!(err.kind(), BootstrapErrorKind::WorkerBinaryMissing);
174 assert!(err.into_report().to_string().contains("worker gone"));
175 }
176
177 #[test]
178 fn bootstrap_error_from_privilege_error_is_other() {
179 let privilege = PrivilegeError::from(eyre!("no privileges"));
180 let err = BootstrapError::from(privilege);
181 assert_eq!(err.kind(), BootstrapErrorKind::Other);
182 assert!(err.to_string().contains("no privileges"));
183 }
184
185 #[test]
186 fn bootstrap_error_from_config_error_is_other() {
187 let config = ConfigError::from(eyre!("bad config"));
188 let err = BootstrapError::from(config);
189 assert_eq!(err.kind(), BootstrapErrorKind::Other);
190 assert!(err.to_string().contains("bad config"));
191 }
192
193 #[rstest]
194 #[case::bootstrap(
195 |report| PgEmbeddedError::Bootstrap(BootstrapError::new(
196 BootstrapErrorKind::WorkerBinaryPathNonUtf8,
197 report,
198 )),
199 BootstrapErrorKind::WorkerBinaryPathNonUtf8,
200 )]
201 #[case::privilege(
202 |report| PgEmbeddedError::Privilege(PrivilegeError::from(report)),
203 BootstrapErrorKind::Other,
204 )]
205 #[case::config(
206 |report| PgEmbeddedError::Config(ConfigError::from(report)),
207 BootstrapErrorKind::Other,
208 )]
209 fn bootstrap_error_from_pg_embedded_error_maps_each_variant(
210 #[case] constructor: fn(Report) -> PgEmbeddedError,
211 #[case] expected_kind: BootstrapErrorKind,
212 ) {
213 let pg_err = constructor(eyre!("inner detail"));
214 let err = BootstrapError::from(pg_err);
215 assert_eq!(err.kind(), expected_kind);
216 assert!(err.to_string().contains("inner detail"));
217 }
218}