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("Fields {fields:?} are inconsistent: {problem}")]
35 Inconsistent {
36 fields: Vec<String>,
38 problem: String,
40 },
41 #[error("Field {field:?} specifies a configuration not supported in this build: {problem}")]
43 NoCompileTimeSupport {
46 field: String,
48 problem: String,
50 },
51}
52
53impl From<derive_builder::UninitializedFieldError> for ConfigBuildError {
54 fn from(val: derive_builder::UninitializedFieldError) -> Self {
55 ConfigBuildError::MissingField {
56 field: val.field_name().to_string(),
57 }
58 }
59}
60
61impl From<derive_builder::SubfieldBuildError<ConfigBuildError>> for ConfigBuildError {
62 fn from(e: derive_builder::SubfieldBuildError<ConfigBuildError>) -> Self {
63 let (field, problem) = e.into_parts();
64 problem.within(field)
65 }
66}
67
68impl From<void::Void> for ConfigBuildError {
69 fn from(value: void::Void) -> Self {
70 void::unreachable(value)
71 }
72}
73
74impl ConfigBuildError {
75 #[must_use]
78 pub fn within(&self, prefix: &str) -> Self {
79 use ConfigBuildError::*;
80 let addprefix = |field: &str| format!("{}.{}", prefix, field);
81 match self {
82 MissingField { field } => MissingField {
83 field: addprefix(field),
84 },
85 Invalid { field, problem } => Invalid {
86 field: addprefix(field),
87 problem: problem.clone(),
88 },
89 Inconsistent { fields, problem } => Inconsistent {
90 fields: fields.iter().map(|f| addprefix(f)).collect(),
91 problem: problem.clone(),
92 },
93 NoCompileTimeSupport { field, problem } => NoCompileTimeSupport {
94 field: addprefix(field),
95 problem: problem.clone(),
96 },
97 }
98 }
99}
100
101impl HasKind for ConfigBuildError {
102 fn kind(&self) -> ErrorKind {
103 ErrorKind::InvalidConfig
104 }
105}
106
107#[derive(Debug, Clone, thiserror::Error)]
109#[non_exhaustive]
110pub enum ReconfigureError {
111 #[error("Cannot change {field} on a running client.")]
113 CannotChange {
114 field: String,
116 },
117
118 #[error("Configuration not supported in this situation: {0}")]
127 UnsupportedSituation(String),
128
129 #[error("Programming error")]
131 Bug(#[from] tor_error::Bug),
132}
133
134impl HasKind for ReconfigureError {
135 fn kind(&self) -> ErrorKind {
136 ErrorKind::InvalidConfigTransition
137 }
138}
139
140#[derive(Debug, Clone, thiserror::Error)]
142#[non_exhaustive]
143pub enum ConfigError {
144 #[error("Problem accessing configuration file(s)")]
146 FileAccess(#[source] fs_mistrust::Error),
147 #[deprecated = "use ConfigError::FileAccess instead"]
152 #[error("Problem accessing configuration file(s)")]
153 Permissions(#[source] fs_mistrust::Error),
154 #[error("Couldn't load configuration")]
157 Load(#[source] ConfigLoadError),
158 #[error("IoError while {} {}", action, path.display_lossy())]
163 Io {
164 action: &'static str,
166 path: PathBuf,
168 #[source]
170 err: std::sync::Arc<std::io::Error>,
171 },
172}
173
174#[derive(Clone, Debug, thiserror::Error)]
176#[non_exhaustive]
177pub enum ConfigGetValueError {
178 #[error("Internal error")]
180 Bug(#[from] tor_error::Bug),
181}
182
183#[derive(Debug, Clone)]
185pub struct ConfigLoadError(figment::Error);
186
187impl ConfigError {
188 pub(crate) fn from_cfg_err(err: figment::Error) -> Self {
193 ConfigError::Load(ConfigLoadError(err))
197 }
198}
199
200impl std::fmt::Display for ConfigLoadError {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 let s = self.0.to_string();
203 write!(f, "{}", s)?;
204 if s.contains("invalid escape") || s.contains("invalid hex escape") {
205 write!(
206 f,
207 " (If you wanted to include a literal \\ character, you need to escape it by writing two in a row: \\\\)"
208 )?;
209 }
210 Ok(())
211 }
212}
213
214impl std::error::Error for ConfigLoadError {
215 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
216 self.0.source()
223 }
224}
225
226#[cfg(test)]
227mod test {
228 #![allow(clippy::bool_assert_comparison)]
230 #![allow(clippy::clone_on_copy)]
231 #![allow(clippy::dbg_macro)]
232 #![allow(clippy::mixed_attributes_style)]
233 #![allow(clippy::print_stderr)]
234 #![allow(clippy::print_stdout)]
235 #![allow(clippy::single_char_pattern)]
236 #![allow(clippy::unwrap_used)]
237 #![allow(clippy::unchecked_time_subtraction)]
238 #![allow(clippy::useless_vec)]
239 #![allow(clippy::needless_pass_by_value)]
240 #![allow(clippy::string_slice)] use super::*;
243
244 #[test]
245 fn within() {
246 let e1 = ConfigBuildError::MissingField {
247 field: "lettuce".to_owned(),
248 };
249 let e2 = ConfigBuildError::Invalid {
250 field: "tomato".to_owned(),
251 problem: "too crunchy".to_owned(),
252 };
253 let e3 = ConfigBuildError::Inconsistent {
254 fields: vec!["mayo".to_owned(), "avocado".to_owned()],
255 problem: "pick one".to_owned(),
256 };
257
258 assert_eq!(
259 &e1.within("sandwich").to_string(),
260 "Field was not provided: sandwich.lettuce"
261 );
262 assert_eq!(
263 &e2.within("sandwich").to_string(),
264 "Value of sandwich.tomato was incorrect: too crunchy"
265 );
266 assert_eq!(
267 &e3.within("sandwich").to_string(),
268 r#"Fields ["sandwich.mayo", "sandwich.avocado"] are inconsistent: pick one"#
269 );
270 }
271
272 #[derive(derive_builder::Builder, Debug, Clone)]
273 #[builder(build_fn(error = "ConfigBuildError"))]
274 #[allow(dead_code)]
275 struct Cephalopod {
276 arms: u8,
278 tentacles: u8,
280 }
281
282 #[test]
283 fn build_err() {
284 let squid = CephalopodBuilder::default().arms(8).tentacles(2).build();
285 let octopus = CephalopodBuilder::default().arms(8).build();
286 assert!(squid.is_ok());
287 let squid = squid.unwrap();
288 assert_eq!(squid.arms, 8);
289 assert_eq!(squid.tentacles, 2);
290 assert!(octopus.is_err());
291 assert_eq!(
292 &octopus.unwrap_err().to_string(),
293 "Field was not provided: tentacles"
294 );
295 }
296
297 #[derive(derive_builder::Builder, Debug)]
298 #[builder(build_fn(error = "ConfigBuildError"))]
299 #[allow(dead_code)]
300 struct Pet {
301 #[builder(sub_builder)]
302 best_friend: Cephalopod,
303 }
304
305 #[test]
306 fn build_subfield_err() {
307 let mut petb = PetBuilder::default();
308 petb.best_friend().tentacles(3);
309 let pet = petb.build();
310 assert_eq!(
311 pet.unwrap_err().to_string(),
312 "Field was not provided: best_friend.arms"
313 );
314 }
315}