use std::sync::Arc;
use tor_error::{ErrorKind, HasKind};
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum ConfigBuildError {
#[error("Field was not provided: {field}")]
MissingField {
field: String,
},
#[error("Value of {field} was incorrect: {problem}")]
Invalid {
field: String,
problem: String,
},
#[error("Fields {fields:?} are inconsistent: {problem}")]
Inconsistent {
fields: Vec<String>,
problem: String,
},
#[error("Field {field:?} specifies a configuration not supported in this build: {problem}")]
NoCompileTimeSupport {
field: String,
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 {
#[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 } => Invalid {
field: addprefix(field),
problem: problem.clone(),
},
}
}
}
impl HasKind for ConfigBuildError {
fn kind(&self) -> ErrorKind {
ErrorKind::InvalidConfig
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum ReconfigureError {
#[error("Cannot change {field} on a running client.")]
CannotChange {
field: String,
},
#[error("Configuration not supported in this situation: {0}")]
UnsupportedSituation(String),
#[error("Programming error")]
Bug(#[from] tor_error::Bug),
}
impl HasKind for ReconfigureError {
fn kind(&self) -> ErrorKind {
ErrorKind::InvalidConfigTransition
}
}
#[derive(Debug, Clone)]
pub struct ConfigError(Arc<config::ConfigError>);
impl From<config::ConfigError> for ConfigError {
fn from(err: config::ConfigError) -> Self {
ConfigError(Arc::new(err))
}
}
impl std::fmt::Display for ConfigError {
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 ConfigError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
impl ConfigError {
pub fn inner(&self) -> &config::ConfigError {
self.0.as_ref()
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![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)]
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: u8,
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"
);
}
}