#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(ci_arti_stable), allow(renamed_and_removed_lints))]
#![cfg_attr(not(ci_arti_nightly), allow(unknown_lints))]
#![deny(missing_docs)]
#![warn(noop_method_call)]
#![deny(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![deny(clippy::missing_panics_doc)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::let_unit_value)] #![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)]
pub mod cmdline;
mod err;
pub mod list_builder;
pub mod load;
mod misc;
mod mut_cfg;
mod path;
pub mod sources;
pub use cmdline::CmdLine;
pub use config as config_crate;
pub use educe;
pub use err::{ConfigBuildError, ConfigError, ReconfigureError};
pub use itertools::Itertools;
pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
pub use misc::*;
pub use mut_cfg::MutCfg;
pub use paste::paste;
pub use path::{CfgPath, CfgPathError};
pub use serde;
pub use sources::{ConfigurationSource, ConfigurationSources};
pub use tor_basic_utils::macro_first_nonempty;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub enum Reconfigure {
AllOrNothing,
WarnOnFailures,
CheckAllOrNothing,
}
impl Reconfigure {
pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
match self {
Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
Err(ReconfigureError::CannotChange {
field: field.as_ref().to_owned(),
})
}
Reconfigure::WarnOnFailures => {
tracing::warn!("Cannot change {} on a running client.", field.as_ref());
Ok(())
}
}
}
}
pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
where
T: Clone + Default + PartialEq,
DF: FnOnce() -> Option<T>,
{
resolve_option_general(
input.as_ref().map(|ov| ov.as_ref()),
|v| v == &T::default(),
def,
)
}
pub fn resolve_option_general<T, ISF, DF>(
input: Option<Option<&T>>,
is_sentinel: ISF,
def: DF,
) -> Option<T>
where
T: Clone,
DF: FnOnce() -> Option<T>,
ISF: FnOnce(&T) -> bool,
{
match input {
None => def(),
Some(None) => None,
Some(Some(v)) if is_sentinel(v) => None,
Some(Some(v)) => Some(v.clone()),
}
}
pub fn resolve_alternative_specs<V, K>(
specified: impl IntoIterator<Item = (K, Option<V>)>,
default: impl FnOnce() -> V,
) -> Result<V, ConfigBuildError>
where
K: Into<String>,
V: Eq,
{
Ok(specified
.into_iter()
.filter_map(|(k, v)| Some((k, v?)))
.dedup_by(|(_, v1), (_, v2)| v1 == v2)
.at_most_one()
.map_err(|several| ConfigBuildError::Inconsistent {
fields: several.into_iter().map(|(k, _v)| k.into()).collect_vec(),
problem: "conflicting fields, specifying different values".into(),
})?
.map(|(_k, v)| v)
.unwrap_or_else(default))
}
#[macro_export]
macro_rules! impl_standard_builder {
{
$Config:ty $(: $($options:tt)* )?
} => { $crate::impl_standard_builder!{
@ ( Builder )
( default )
( try_deserialize ) $Config : $( $( $options )* )?
} };
{
@ ( $($Builder :ident)? )
( $($default :ident)? )
( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Deserialize $( $options:tt )*
} => { $crate::impl_standard_builder!{
@ ( $($Builder )? )
( $($default )? )
( ) $Config : $( $options )*
} };
{
@ ( $($Builder :ident)? )
( $($default :ident)? )
( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Builder $( $options:tt )*
} => { $crate::impl_standard_builder!{
@ ( )
( $($default )? )
( $($try_deserialize )? ) $Config : $( $options )*
} };
{
@ ( $($Builder :ident)? )
( $($default :ident)? )
( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Default $( $options:tt )*
} => { $crate::impl_standard_builder!{
@ ( $($Builder )? )
( )
( $($try_deserialize )? ) $Config : $( $options )*
} };
{
@ ( $($Builder :ident)? )
( $($default :ident)? )
( $($try_deserialize:ident)? ) $Config:ty : $(+)?
} => { $crate::paste!{
impl $Config {
pub fn builder() -> [< $Config Builder >] {
Default::default()
}
}
$( impl Default for $Config {
fn $default() -> Self {
[< $Config Builder >]::default().build().unwrap()
}
}
)?
$( impl $crate::load::$Builder for [< $Config Builder >] {
type Built = $Config;
fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
[< $Config Builder >]::build(self)
}
}
)?
#[test]
#[allow(non_snake_case)]
fn [< test_impl_Default_for_ $Config >] () {
#[allow(unused_variables)]
let def = None::<$Config>;
$( let def = Some($Config::$default());
)?
if let Some(def) = def {
$( let empty_config = $crate::config_crate::Config::builder().build().unwrap();
let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
let from_empty = builder.build().unwrap();
assert_eq!(def, from_empty);
)*
}
}
} };
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod test {
use super::*;
use crate as tor_config;
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tracing_test::traced_test;
#[test]
#[traced_test]
fn reconfigure_helpers() {
let how = Reconfigure::AllOrNothing;
let err = how.cannot_change("the_laws_of_physics").unwrap_err();
assert_eq!(
err.to_string(),
"Cannot change the_laws_of_physics on a running client.".to_owned()
);
let how = Reconfigure::WarnOnFailures;
let ok = how.cannot_change("stuff");
assert!(ok.is_ok());
assert!(logs_contain("Cannot change stuff on a running client."));
}
#[test]
#[rustfmt::skip] fn resolve_option_test() {
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
#[builder(build_fn(error = "ConfigBuildError"))]
#[builder(derive(Debug, Serialize, Deserialize, Eq, PartialEq))]
struct TestConfig {
#[builder(field(build = r#"tor_config::resolve_option(&self.none, || None)"#))]
none: Option<u32>,
#[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
four: Option<u32>,
}
{
let builder_from_json: TestConfigBuilder = serde_json::from_value(
json!{ { } }
).unwrap();
let builder_from_methods = TestConfigBuilder::default();
assert_eq!(builder_from_methods, builder_from_json);
assert_eq!(builder_from_methods.build().unwrap(),
TestConfig { none: None, four: Some(4) });
}
{
let builder_from_json: TestConfigBuilder = serde_json::from_value(
json!{ { "none": 123, "four": 456 } }
).unwrap();
let mut builder_from_methods = TestConfigBuilder::default();
builder_from_methods.none(Some(123));
builder_from_methods.four(Some(456));
assert_eq!(builder_from_methods, builder_from_json);
assert_eq!(builder_from_methods.build().unwrap(),
TestConfig { none: Some(123), four: Some(456) });
}
{
let builder_from_json: TestConfigBuilder = serde_json::from_value(
json!{ { "none": 0, "four": 0 } }
).unwrap();
let mut builder_from_methods = TestConfigBuilder::default();
builder_from_methods.none(Some(0));
builder_from_methods.four(Some(0));
assert_eq!(builder_from_methods, builder_from_json);
assert_eq!(builder_from_methods.build().unwrap(),
TestConfig { none: None, four: None });
}
{
let mut builder_from_methods = TestConfigBuilder::default();
builder_from_methods.none(None);
builder_from_methods.four(None);
assert_eq!(builder_from_methods.build().unwrap(),
TestConfig { none: None, four: None });
}
}
}