1use std::path::PathBuf;
4
5use tor_basic_utils::PathExt as _;
6use tor_error::{ErrorKind, HasKind};
7
8#[derive(Debug, Clone, thiserror::Error)]
17#[non_exhaustive]
18pub enum ConfigBuildError {
19 #[error("Field was not provided: {field}")]
21 MissingField {
22 field: String,
24 },
25 #[error("Value of {field} was incorrect: {problem}")]
27 Invalid {
28 field: String,
30 problem: String,
32 },
33 #[error("At least {min_required} of these fields must be provided: {fields:?}")]
36 MissingOneOf {
37 min_required: usize,
39 fields: Vec<String>,
41 },
42 #[error("Fields {fields:?} are inconsistent: {problem}")]
44 Inconsistent {
45 fields: Vec<String>,
47 problem: String,
49 },
50 #[error("Field {field:?} specifies a configuration not supported in this build: {problem}")]
52 NoCompileTimeSupport {
55 field: String,
57 problem: String,
59 },
60}
61
62impl From<derive_builder::UninitializedFieldError> for ConfigBuildError {
63 fn from(val: derive_builder::UninitializedFieldError) -> Self {
64 ConfigBuildError::MissingField {
65 field: val.field_name().to_string(),
66 }
67 }
68}
69
70impl From<derive_builder::SubfieldBuildError<ConfigBuildError>> for ConfigBuildError {
71 fn from(e: derive_builder::SubfieldBuildError<ConfigBuildError>) -> Self {
72 let (field, problem) = e.into_parts();
73 problem.within(field)
74 }
75}
76
77impl From<void::Void> for ConfigBuildError {
78 fn from(value: void::Void) -> Self {
79 void::unreachable(value)
80 }
81}
82
83impl ConfigBuildError {
84 #[must_use]
87 pub fn within(&self, prefix: &str) -> Self {
88 use ConfigBuildError::*;
89 let addprefix = |field: &str| format!("{}.{}", prefix, field);
90 match self {
91 MissingField { field } => MissingField {
92 field: addprefix(field),
93 },
94 MissingOneOf {
95 min_required,
96 fields,
97 } => MissingOneOf {
98 min_required: *min_required,
99 fields: fields.iter().map(|f| addprefix(f)).collect(),
100 },
101 Invalid { field, problem } => Invalid {
102 field: addprefix(field),
103 problem: problem.clone(),
104 },
105 Inconsistent { fields, problem } => Inconsistent {
106 fields: fields.iter().map(|f| addprefix(f)).collect(),
107 problem: problem.clone(),
108 },
109 NoCompileTimeSupport { field, problem } => NoCompileTimeSupport {
110 field: addprefix(field),
111 problem: problem.clone(),
112 },
113 }
114 }
115}
116
117impl HasKind for ConfigBuildError {
118 fn kind(&self) -> ErrorKind {
119 ErrorKind::InvalidConfig
120 }
121}
122
123#[derive(Debug, Clone, thiserror::Error)]
125#[non_exhaustive]
126pub enum ReconfigureError {
127 #[error("Cannot change {field} on a running client.")]
129 CannotChange {
130 field: String,
132 },
133
134 #[error("Cannot change {field} {manner} on a running client.")]
139 CannotChangeToValue {
140 field: String,
142 manner: String,
144 },
145
146 #[error("Configuration not supported in this situation: {0}")]
155 UnsupportedSituation(String),
156
157 #[error("Programming error")]
159 Bug(#[from] tor_error::Bug),
160}
161
162impl HasKind for ReconfigureError {
163 fn kind(&self) -> ErrorKind {
164 ErrorKind::InvalidConfigTransition
165 }
166}
167
168#[derive(Debug, Clone, thiserror::Error)]
170#[non_exhaustive]
171pub enum ConfigError {
172 #[error("Problem accessing configuration file(s)")]
174 FileAccess(#[source] fs_mistrust::Error),
175 #[deprecated = "use ConfigError::FileAccess instead"]
180 #[error("Problem accessing configuration file(s)")]
181 Permissions(#[source] fs_mistrust::Error),
182 #[error("Couldn't load configuration")]
185 Load(#[source] ConfigLoadError),
186 #[error("IoError while {} {}", action, path.display_lossy())]
191 Io {
192 action: &'static str,
194 path: PathBuf,
196 #[source]
198 err: std::sync::Arc<std::io::Error>,
199 },
200}
201
202#[derive(Clone, Debug, thiserror::Error)]
204#[non_exhaustive]
205pub enum ConfigGetValueError {
206 #[error("Internal error")]
208 Bug(#[from] tor_error::Bug),
209}
210
211#[derive(Debug, Clone)]
213pub struct ConfigLoadError(figment::Error);
214
215impl ConfigError {
216 pub(crate) fn from_cfg_err(err: figment::Error) -> Self {
221 ConfigError::Load(ConfigLoadError(err))
225 }
226}
227
228impl std::fmt::Display for ConfigLoadError {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 let s = self.0.to_string();
231 write!(f, "{}", s)?;
232 if s.contains("invalid escape") || s.contains("invalid hex escape") {
233 write!(
234 f,
235 " (If you wanted to include a literal \\ character, you need to escape it by writing two in a row: \\\\)"
236 )?;
237 }
238 Ok(())
239 }
240}
241
242impl std::error::Error for ConfigLoadError {
243 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
244 self.0.source()
251 }
252}
253
254#[cfg(test)]
255mod test {
256 #![allow(clippy::bool_assert_comparison)]
258 #![allow(clippy::clone_on_copy)]
259 #![allow(clippy::dbg_macro)]
260 #![allow(clippy::mixed_attributes_style)]
261 #![allow(clippy::print_stderr)]
262 #![allow(clippy::print_stdout)]
263 #![allow(clippy::single_char_pattern)]
264 #![allow(clippy::unwrap_used)]
265 #![allow(clippy::unchecked_time_subtraction)]
266 #![allow(clippy::useless_vec)]
267 #![allow(clippy::needless_pass_by_value)]
268 #![allow(clippy::string_slice)] use super::*;
271
272 #[test]
273 fn within() {
274 let e1 = ConfigBuildError::MissingField {
275 field: "lettuce".to_owned(),
276 };
277 let e2 = ConfigBuildError::Invalid {
278 field: "tomato".to_owned(),
279 problem: "too crunchy".to_owned(),
280 };
281 let e3 = ConfigBuildError::Inconsistent {
282 fields: vec!["mayo".to_owned(), "avocado".to_owned()],
283 problem: "pick one".to_owned(),
284 };
285
286 assert_eq!(
287 &e1.within("sandwich").to_string(),
288 "Field was not provided: sandwich.lettuce"
289 );
290 assert_eq!(
291 &e2.within("sandwich").to_string(),
292 "Value of sandwich.tomato was incorrect: too crunchy"
293 );
294 assert_eq!(
295 &e3.within("sandwich").to_string(),
296 r#"Fields ["sandwich.mayo", "sandwich.avocado"] are inconsistent: pick one"#
297 );
298 }
299
300 #[derive(derive_builder::Builder, Debug, Clone)]
301 #[builder(build_fn(error = "ConfigBuildError"))]
302 #[allow(dead_code)]
303 struct Cephalopod {
304 arms: u8,
306 tentacles: u8,
308 }
309
310 #[test]
311 fn build_err() {
312 let squid = CephalopodBuilder::default().arms(8).tentacles(2).build();
313 let octopus = CephalopodBuilder::default().arms(8).build();
314 assert!(squid.is_ok());
315 let squid = squid.unwrap();
316 assert_eq!(squid.arms, 8);
317 assert_eq!(squid.tentacles, 2);
318 assert!(octopus.is_err());
319 assert_eq!(
320 &octopus.unwrap_err().to_string(),
321 "Field was not provided: tentacles"
322 );
323 }
324
325 #[derive(derive_builder::Builder, Debug)]
326 #[builder(build_fn(error = "ConfigBuildError"))]
327 #[allow(dead_code)]
328 struct Pet {
329 #[builder(sub_builder)]
330 best_friend: Cephalopod,
331 }
332
333 #[test]
334 fn build_subfield_err() {
335 let mut petb = PetBuilder::default();
336 petb.best_friend().tentacles(3);
337 let pet = petb.build();
338 assert_eq!(
339 pet.unwrap_err().to_string(),
340 "Field was not provided: best_friend.arms"
341 );
342 }
343}