1//! Declare error types.
23use std::path::PathBuf;
45use tor_basic_utils::PathExt as _;
6use tor_error::{ErrorKind, HasKind};
78/// An error related to an option passed to Arti via a configuration
9/// builder.
10//
11// API NOTE: When possible, we should expose this error type rather than
12// wrapping it in `TorError`. It can provide specific information about what
13// part of the configuration was invalid.
14//
15// This is part of the public API.
16#[derive(Debug, Clone, thiserror::Error)]
17#[non_exhaustive]
18pub enum ConfigBuildError {
19/// A mandatory field was not present.
20#[error("Field was not provided: {field}")]
21MissingField {
22/// The name of the missing field.
23field: String,
24 },
25/// A single field had a value that proved to be unusable.
26#[error("Value of {field} was incorrect: {problem}")]
27Invalid {
28/// The name of the invalid field
29field: String,
30/// A description of the problem.
31problem: String,
32 },
33/// Multiple fields are inconsistent.
34#[error("Fields {fields:?} are inconsistent: {problem}")]
35Inconsistent {
36/// The names of the inconsistent fields
37fields: Vec<String>,
38/// The problem that makes them inconsistent
39problem: String,
40 },
41/// The requested configuration is not supported in this build
42#[error("Field {field:?} specifies a configuration not supported in this build: {problem}")]
43// TODO should we report the cargo feature, if applicable? And if so, of `arti`
44 // or of the underlying crate? This seems like a can of worms.
45NoCompileTimeSupport {
46/// The names of the (primary) field requesting the unsupported configuration
47field: String,
48/// The description of the problem
49problem: String,
50 },
51}
5253impl From<derive_builder::UninitializedFieldError> for ConfigBuildError {
54fn from(val: derive_builder::UninitializedFieldError) -> Self {
55 ConfigBuildError::MissingField {
56 field: val.field_name().to_string(),
57 }
58 }
59}
6061impl From<derive_builder::SubfieldBuildError<ConfigBuildError>> for ConfigBuildError {
62fn from(e: derive_builder::SubfieldBuildError<ConfigBuildError>) -> Self {
63let (field, problem) = e.into_parts();
64 problem.within(field)
65 }
66}
6768impl ConfigBuildError {
69/// Return a new ConfigBuildError that prefixes its field name with
70 /// `prefix` and a dot.
71#[must_use]
72pub fn within(&self, prefix: &str) -> Self {
73use ConfigBuildError::*;
74let addprefix = |field: &str| format!("{}.{}", prefix, field);
75match 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}
9495impl HasKind for ConfigBuildError {
96fn kind(&self) -> ErrorKind {
97 ErrorKind::InvalidConfig
98 }
99}
100101/// An error caused when attempting to reconfigure an existing Arti client, or one of its modules.
102#[derive(Debug, Clone, thiserror::Error)]
103#[non_exhaustive]
104pub enum ReconfigureError {
105/// Tried to change a field that cannot change on a running client.
106#[error("Cannot change {field} on a running client.")]
107CannotChange {
108/// The field (or fields) that we tried to change.
109field: String,
110 },
111112/// The requested configuration is not supported in this situation
113 ///
114 /// Something, probably discovered at runtime, is not compatible with
115 /// the specified configuration.
116 ///
117 /// This ought *not* to be returned when the configuration is simply not supported
118 /// by this build of arti -
119 /// that should be reported at config build type as `ConfigBuildError::Unsupported`.
120#[error("Configuration not supported in this situation: {0}")]
121UnsupportedSituation(String),
122123/// There was a programming error somewhere in our code, or the calling code.
124#[error("Programming error")]
125Bug(#[from] tor_error::Bug),
126}
127128impl HasKind for ReconfigureError {
129fn kind(&self) -> ErrorKind {
130 ErrorKind::InvalidConfigTransition
131 }
132}
133134/// An error that occurs while trying to read and process our configuration.
135#[derive(Debug, Clone, thiserror::Error)]
136#[non_exhaustive]
137pub enum ConfigError {
138/// We encoundered a problem checking file permissions (for example, no such file)
139#[error("Problem accessing configuration file(s)")]
140FileAccess(#[source] fs_mistrust::Error),
141/// We encoundered a problem checking file permissions (for example, no such file)
142 ///
143 /// This variant name is misleading - see the docs for [`fs_mistrust::Error`].
144 /// Please use [`ConfigError::FileAccess`] instead.
145#[deprecated = "use ConfigError::FileAccess instead"]
146 #[error("Problem accessing configuration file(s)")]
147Permissions(#[source] fs_mistrust::Error),
148/// Our underlying configuration library gave an error while loading our
149 /// configuration.
150#[error("Couldn't load configuration")]
151Load(#[source] ConfigLoadError),
152/// Encountered an IO error with a configuration file or directory.
153 ///
154 /// Note that some IO errors may be reported as `Load` errors,
155 /// due to limitations of the underlying library.
156#[error("IoError while {} {}", action, path.display_lossy())]
157Io {
158/// The action while we were trying to perform
159action: &'static str,
160/// The path we were trying to do it to.
161path: PathBuf,
162/// The underlying problem
163#[source]
164err: std::sync::Arc<std::io::Error>,
165 },
166}
167168/// Wrapper for our an error type from our underlying configuration library.
169#[derive(Debug, Clone)]
170pub struct ConfigLoadError(figment::Error);
171172impl ConfigError {
173/// Wrap `err` as a ConfigError.
174 ///
175 /// This is not a From implementation, since we don't want to expose our
176 /// underlying configuration library.
177pub(crate) fn from_cfg_err(err: figment::Error) -> Self {
178// TODO: It would be lovely to extract IO errors from figment::Error
179 // and report them as Error::Io. Unfortunately, it doesn't seem
180 // possible to do that given the design of figment::Error.
181ConfigError::Load(ConfigLoadError(err))
182 }
183}
184185impl std::fmt::Display for ConfigLoadError {
186fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187let s = self.0.to_string();
188write!(f, "{}", s)?;
189if s.contains("invalid escape") || s.contains("invalid hex escape") {
190write!(f, " (If you wanted to include a literal \\ character, you need to escape it by writing two in a row: \\\\)")?;
191 }
192Ok(())
193 }
194}
195196impl std::error::Error for ConfigLoadError {
197fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
198Some(&self.0)
199 }
200}
201202#[cfg(test)]
203mod test {
204// @@ begin test lint list maintained by maint/add_warning @@
205#![allow(clippy::bool_assert_comparison)]
206 #![allow(clippy::clone_on_copy)]
207 #![allow(clippy::dbg_macro)]
208 #![allow(clippy::mixed_attributes_style)]
209 #![allow(clippy::print_stderr)]
210 #![allow(clippy::print_stdout)]
211 #![allow(clippy::single_char_pattern)]
212 #![allow(clippy::unwrap_used)]
213 #![allow(clippy::unchecked_duration_subtraction)]
214 #![allow(clippy::useless_vec)]
215 #![allow(clippy::needless_pass_by_value)]
216//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
217use super::*;
218219#[test]
220fn within() {
221let e1 = ConfigBuildError::MissingField {
222 field: "lettuce".to_owned(),
223 };
224let e2 = ConfigBuildError::Invalid {
225 field: "tomato".to_owned(),
226 problem: "too crunchy".to_owned(),
227 };
228let e3 = ConfigBuildError::Inconsistent {
229 fields: vec!["mayo".to_owned(), "avocado".to_owned()],
230 problem: "pick one".to_owned(),
231 };
232233assert_eq!(
234&e1.within("sandwich").to_string(),
235"Field was not provided: sandwich.lettuce"
236);
237assert_eq!(
238&e2.within("sandwich").to_string(),
239"Value of sandwich.tomato was incorrect: too crunchy"
240);
241assert_eq!(
242&e3.within("sandwich").to_string(),
243r#"Fields ["sandwich.mayo", "sandwich.avocado"] are inconsistent: pick one"#
244);
245 }
246247#[derive(derive_builder::Builder, Debug, Clone)]
248 #[builder(build_fn(error = "ConfigBuildError"))]
249 #[allow(dead_code)]
250struct Cephalopod {
251// arms have suction cups for their whole length
252arms: u8,
253// Tentacles have suction cups at the ends
254tentacles: u8,
255 }
256257#[test]
258fn build_err() {
259let squid = CephalopodBuilder::default().arms(8).tentacles(2).build();
260let octopus = CephalopodBuilder::default().arms(8).build();
261assert!(squid.is_ok());
262let squid = squid.unwrap();
263assert_eq!(squid.arms, 8);
264assert_eq!(squid.tentacles, 2);
265assert!(octopus.is_err());
266assert_eq!(
267&octopus.unwrap_err().to_string(),
268"Field was not provided: tentacles"
269);
270 }
271272#[derive(derive_builder::Builder, Debug)]
273 #[builder(build_fn(error = "ConfigBuildError"))]
274 #[allow(dead_code)]
275struct Pet {
276#[builder(sub_builder)]
277best_friend: Cephalopod,
278 }
279280#[test]
281fn build_subfield_err() {
282let mut petb = PetBuilder::default();
283 petb.best_friend().tentacles(3);
284let pet = petb.build();
285assert_eq!(
286 pet.unwrap_err().to_string(),
287"Field was not provided: best_friend.arms"
288);
289 }
290}