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 ClusterPasswordMissing,
44 ClusterPasswordUnreadable,
47 ClusterPasswordEmpty,
49 ExtensionConfigInvalid,
55 ExtensionManifestUnavailable,
57 ExtensionManifestDigestMismatch,
59 ExtensionManifestInvalid,
62 ExtensionUnavailable,
65 ExtensionArchiveUnavailable,
67 ExtensionArchiveDigestMismatch,
69 ExtensionArchiveInvalid,
72 ExtensionInstallFailed,
74}
75
76#[derive(Debug, Error)]
78#[error("{report}")]
79pub struct BootstrapError {
80 kind: BootstrapErrorKind,
81 #[source]
82 report: Report,
83}
84
85impl BootstrapError {
86 #[must_use]
89 pub const fn new(kind: BootstrapErrorKind, report: Report) -> Self { Self { kind, report } }
90
91 #[must_use]
93 pub const fn kind(&self) -> BootstrapErrorKind { self.kind }
94
95 pub fn into_report(self) -> Report { self.report }
97}
98
99impl From<Report> for BootstrapError {
100 fn from(report: Report) -> Self { Self::new(BootstrapErrorKind::Other, report) }
101}
102
103impl From<PrivilegeError> for BootstrapError {
104 fn from(err: PrivilegeError) -> Self {
105 let PrivilegeError(report) = err;
106 Self::new(BootstrapErrorKind::Other, report)
107 }
108}
109
110impl From<ConfigError> for BootstrapError {
111 fn from(err: ConfigError) -> Self {
112 let ConfigError(report) = err;
113 Self::new(BootstrapErrorKind::Other, report)
114 }
115}
116
117impl From<PgEmbeddedError> for BootstrapError {
118 fn from(err: PgEmbeddedError) -> Self {
119 match err {
120 PgEmbeddedError::Bootstrap(inner) => inner,
121 PgEmbeddedError::Privilege(inner) => inner.into(),
122 PgEmbeddedError::Config(inner) => inner.into(),
123 }
124 }
125}
126
127#[derive(Debug, Error)]
129#[error(transparent)]
130pub struct PrivilegeError(#[from] Report);
131
132#[derive(Debug, Error)]
134#[error(transparent)]
135pub struct ConfigError(#[from] Report);
136
137#[cfg(test)]
138mod tests {
139 use color_eyre::eyre::eyre;
142 use rstest::rstest;
143
144 use super::*;
145
146 #[rstest]
147 #[case::bootstrap(
148 "PG_EMBEDDED_WORKER must be set",
149 "bootstrap failed:",
150 |msg: &str| PgEmbeddedError::Bootstrap(BootstrapError::from(eyre!("{}", msg)))
151 )]
152 #[case::privilege(
153 "failed to drop privileges",
154 "privilege management failed:",
155 |msg: &str| PgEmbeddedError::Privilege(PrivilegeError::from(eyre!("{}", msg)))
156 )]
157 #[case::config(
158 "invalid port number",
159 "configuration parsing failed:",
160 |msg: &str| PgEmbeddedError::Config(ConfigError::from(eyre!("{}", msg)))
161 )]
162 fn pg_embedded_error_includes_inner_message(
163 #[case] inner_message: &str,
164 #[case] expected_prefix: &str,
165 #[case] constructor: fn(&str) -> PgEmbeddedError,
166 ) {
167 let pg_err = constructor(inner_message);
168
169 let display = pg_err.to_string();
170
171 assert!(
172 display.contains(expected_prefix),
173 "expected '{expected_prefix}' prefix, got: {display}"
174 );
175 assert!(
176 display.contains(inner_message),
177 "expected inner message '{inner_message}' in display, got: {display}"
178 );
179 }
180
181 #[test]
182 fn bootstrap_error_displays_report_message() {
183 let inner_message = "database connection failed";
184 let err = BootstrapError::from(eyre!(inner_message));
185
186 let display = err.to_string();
187
188 assert!(
189 display.contains(inner_message),
190 "expected '{inner_message}' in display, got: {display}"
191 );
192 }
193
194 #[test]
195 fn bootstrap_error_kind_defaults_to_other() {
196 assert_eq!(BootstrapErrorKind::default(), BootstrapErrorKind::Other);
197 }
198
199 #[test]
200 fn bootstrap_error_preserves_kind_and_into_report() {
201 let err = BootstrapError::new(
202 BootstrapErrorKind::WorkerBinaryMissing,
203 eyre!("worker gone"),
204 );
205 assert_eq!(err.kind(), BootstrapErrorKind::WorkerBinaryMissing);
206 assert!(err.into_report().to_string().contains("worker gone"));
207 }
208
209 #[rstest]
210 #[case::config(BootstrapErrorKind::ExtensionConfigInvalid)]
211 #[case::manifest_unavailable(BootstrapErrorKind::ExtensionManifestUnavailable)]
212 #[case::manifest_digest(BootstrapErrorKind::ExtensionManifestDigestMismatch)]
213 #[case::manifest_invalid(BootstrapErrorKind::ExtensionManifestInvalid)]
214 #[case::unavailable(BootstrapErrorKind::ExtensionUnavailable)]
215 #[case::archive_unavailable(BootstrapErrorKind::ExtensionArchiveUnavailable)]
216 #[case::archive_digest(BootstrapErrorKind::ExtensionArchiveDigestMismatch)]
217 #[case::archive_invalid(BootstrapErrorKind::ExtensionArchiveInvalid)]
218 #[case::install_failed(BootstrapErrorKind::ExtensionInstallFailed)]
219 fn extension_error_kinds_survive_wrapping(#[case] kind: BootstrapErrorKind) {
220 let err = BootstrapError::new(kind, eyre!("extension detail"));
221 assert_eq!(err.kind(), kind);
222 let wrapped = BootstrapError::from(PgEmbeddedError::Bootstrap(err));
223 assert_eq!(wrapped.kind(), kind);
224 assert!(wrapped.to_string().contains("extension detail"));
225 }
226
227 #[test]
228 fn bootstrap_error_from_privilege_error_is_other() {
229 let privilege = PrivilegeError::from(eyre!("no privileges"));
230 let err = BootstrapError::from(privilege);
231 assert_eq!(err.kind(), BootstrapErrorKind::Other);
232 assert!(err.to_string().contains("no privileges"));
233 }
234
235 #[test]
236 fn bootstrap_error_from_config_error_is_other() {
237 let config = ConfigError::from(eyre!("bad config"));
238 let err = BootstrapError::from(config);
239 assert_eq!(err.kind(), BootstrapErrorKind::Other);
240 assert!(err.to_string().contains("bad config"));
241 }
242
243 #[rstest]
244 #[case::bootstrap(
245 |report| PgEmbeddedError::Bootstrap(BootstrapError::new(
246 BootstrapErrorKind::WorkerBinaryPathNonUtf8,
247 report,
248 )),
249 BootstrapErrorKind::WorkerBinaryPathNonUtf8,
250 )]
251 #[case::privilege(
252 |report| PgEmbeddedError::Privilege(PrivilegeError::from(report)),
253 BootstrapErrorKind::Other,
254 )]
255 #[case::config(
256 |report| PgEmbeddedError::Config(ConfigError::from(report)),
257 BootstrapErrorKind::Other,
258 )]
259 fn bootstrap_error_from_pg_embedded_error_maps_each_variant(
260 #[case] constructor: fn(Report) -> PgEmbeddedError,
261 #[case] expected_kind: BootstrapErrorKind,
262 ) {
263 let pg_err = constructor(eyre!("inner detail"));
264 let err = BootstrapError::from(pg_err);
265 assert_eq!(err.kind(), expected_kind);
266 assert!(err.to_string().contains("inner detail"));
267 }
268}