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