Lines
58.82 %
Functions
69.7 %
Branches
100 %
//! 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
/// 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
/// 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
/// The description of the problem
}
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 {
problem: problem.clone(),
Inconsistent { fields, problem } => Inconsistent {
fields: fields.iter().map(|f| addprefix(f)).collect(),
NoCompileTimeSupport { field, problem } => NoCompileTimeSupport {
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.
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.
/// 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 {
ErrorKind::InvalidConfigTransition
/// An error that occurs while trying to read and process our configuration.
pub enum ConfigError {
/// We encoundered a problem checking file permissions (for example, no such file)
#[error("Problem accessing configuration file(s)")]
FileAccess(#[source] fs_mistrust::Error),
/// This variant name is misleading - see the docs for [`fs_mistrust::Error`].
/// Please use [`ConfigError::FileAccess`] instead.
#[deprecated = "use ConfigError::FileAccess instead"]
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"
);
&e2.within("sandwich").to_string(),
"Value of sandwich.tomato was incorrect: too crunchy"
&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,
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());
&octopus.unwrap_err().to_string(),
"Field was not provided: tentacles"
#[derive(derive_builder::Builder, Debug)]
struct Pet {
#[builder(sub_builder)]
best_friend: Cephalopod,
fn build_subfield_err() {
let mut petb = PetBuilder::default();
petb.best_friend().tentacles(3);
let pet = petb.build();
pet.unwrap_err().to_string(),
"Field was not provided: best_friend.arms"