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 ConfigBuildError {
69 #[must_use]
72 pub fn within(&self, prefix: &str) -> Self {
73 use ConfigBuildError::*;
74 let addprefix = |field: &str| format!("{}.{}", prefix, field);
75 match self {
76 MissingField { field } => MissingField {
77 field: addprefix(field),
78 },
79 Invalid { field, problem } => Invalid {
80 field: addprefix(field),
81 problem: problem.clone(),
82 },
83 Inconsistent { fields, problem } => Inconsistent {
84 fields: fields.iter().map(|f| addprefix(f)).collect(),
85 problem: problem.clone(),
86 },
87 NoCompileTimeSupport { field, problem } => NoCompileTimeSupport {
88 field: addprefix(field),
89 problem: problem.clone(),
90 },
91 }
92 }
93}
94
95impl HasKind for ConfigBuildError {
96 fn kind(&self) -> ErrorKind {
97 ErrorKind::InvalidConfig
98 }
99}
100
101#[derive(Debug, Clone, thiserror::Error)]
103#[non_exhaustive]
104pub enum ReconfigureError {
105 #[error("Cannot change {field} on a running client.")]
107 CannotChange {
108 field: String,
110 },
111
112 #[error("Configuration not supported in this situation: {0}")]
121 UnsupportedSituation(String),
122
123 #[error("Programming error")]
125 Bug(#[from] tor_error::Bug),
126}
127
128impl HasKind for ReconfigureError {
129 fn kind(&self) -> ErrorKind {
130 ErrorKind::InvalidConfigTransition
131 }
132}
133
134#[derive(Debug, Clone, thiserror::Error)]
136#[non_exhaustive]
137pub enum ConfigError {
138 #[error("Problem accessing configuration file(s)")]
140 FileAccess(#[source] fs_mistrust::Error),
141 #[deprecated = "use ConfigError::FileAccess instead"]
146 #[error("Problem accessing configuration file(s)")]
147 Permissions(#[source] fs_mistrust::Error),
148 #[error("Couldn't load configuration")]
151 Load(#[source] ConfigLoadError),
152 #[error("IoError while {} {}", action, path.display_lossy())]
157 Io {
158 action: &'static str,
160 path: PathBuf,
162 #[source]
164 err: std::sync::Arc<std::io::Error>,
165 },
166}
167
168#[derive(Debug, Clone)]
170pub struct ConfigLoadError(figment::Error);
171
172impl ConfigError {
173 pub(crate) fn from_cfg_err(err: figment::Error) -> Self {
178 ConfigError::Load(ConfigLoadError(err))
182 }
183}
184
185impl std::fmt::Display for ConfigLoadError {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 let s = self.0.to_string();
188 write!(f, "{}", s)?;
189 if s.contains("invalid escape") || s.contains("invalid hex escape") {
190 write!(
191 f,
192 " (If you wanted to include a literal \\ character, you need to escape it by writing two in a row: \\\\)"
193 )?;
194 }
195 Ok(())
196 }
197}
198
199impl std::error::Error for ConfigLoadError {
200 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
201 self.0.source()
208 }
209}
210
211#[cfg(test)]
212mod test {
213 #![allow(clippy::bool_assert_comparison)]
215 #![allow(clippy::clone_on_copy)]
216 #![allow(clippy::dbg_macro)]
217 #![allow(clippy::mixed_attributes_style)]
218 #![allow(clippy::print_stderr)]
219 #![allow(clippy::print_stdout)]
220 #![allow(clippy::single_char_pattern)]
221 #![allow(clippy::unwrap_used)]
222 #![allow(clippy::unchecked_time_subtraction)]
223 #![allow(clippy::useless_vec)]
224 #![allow(clippy::needless_pass_by_value)]
225 use super::*;
227
228 #[test]
229 fn within() {
230 let e1 = ConfigBuildError::MissingField {
231 field: "lettuce".to_owned(),
232 };
233 let e2 = ConfigBuildError::Invalid {
234 field: "tomato".to_owned(),
235 problem: "too crunchy".to_owned(),
236 };
237 let e3 = ConfigBuildError::Inconsistent {
238 fields: vec!["mayo".to_owned(), "avocado".to_owned()],
239 problem: "pick one".to_owned(),
240 };
241
242 assert_eq!(
243 &e1.within("sandwich").to_string(),
244 "Field was not provided: sandwich.lettuce"
245 );
246 assert_eq!(
247 &e2.within("sandwich").to_string(),
248 "Value of sandwich.tomato was incorrect: too crunchy"
249 );
250 assert_eq!(
251 &e3.within("sandwich").to_string(),
252 r#"Fields ["sandwich.mayo", "sandwich.avocado"] are inconsistent: pick one"#
253 );
254 }
255
256 #[derive(derive_builder::Builder, Debug, Clone)]
257 #[builder(build_fn(error = "ConfigBuildError"))]
258 #[allow(dead_code)]
259 struct Cephalopod {
260 arms: u8,
262 tentacles: u8,
264 }
265
266 #[test]
267 fn build_err() {
268 let squid = CephalopodBuilder::default().arms(8).tentacles(2).build();
269 let octopus = CephalopodBuilder::default().arms(8).build();
270 assert!(squid.is_ok());
271 let squid = squid.unwrap();
272 assert_eq!(squid.arms, 8);
273 assert_eq!(squid.tentacles, 2);
274 assert!(octopus.is_err());
275 assert_eq!(
276 &octopus.unwrap_err().to_string(),
277 "Field was not provided: tentacles"
278 );
279 }
280
281 #[derive(derive_builder::Builder, Debug)]
282 #[builder(build_fn(error = "ConfigBuildError"))]
283 #[allow(dead_code)]
284 struct Pet {
285 #[builder(sub_builder)]
286 best_friend: Cephalopod,
287 }
288
289 #[test]
290 fn build_subfield_err() {
291 let mut petb = PetBuilder::default();
292 petb.best_friend().tentacles(3);
293 let pet = petb.build();
294 assert_eq!(
295 pet.unwrap_err().to_string(),
296 "Field was not provided: best_friend.arms"
297 );
298 }
299}