tor-config: Provide BoolOrAuto

We're going to use this for the config item `bridges.enabled`,
but it seems general enough that it ought to go here.
This commit is contained in:
Ian Jackson 2022-10-07 14:19:33 +01:00
parent b766b16e46
commit 4a6ac4090c
1 changed files with 107 additions and 0 deletions

View File

@ -14,6 +14,84 @@ use itertools::Itertools;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString, IntoStaticStr};
/// Boolean, but with additional `"auto"` option
//
// This slightly-odd interleaving of derives and attributes stops rustfmt doing a daft thing
#[derive(Clone, Copy, Hash, Debug, Ord, PartialOrd, Eq, PartialEq)]
#[allow(clippy::exhaustive_enums)] // we will add variants very rarely if ever
#[derive(Serialize, Deserialize)]
#[serde(try_from = "BoolOrAutoSerde", into = "BoolOrAutoSerde")]
#[derive(Educe)]
#[educe(Default)]
pub enum BoolOrAuto {
#[educe(Default)]
/// Automatic
Auto,
/// Explicitly specified
Explicit(bool),
}
impl BoolOrAuto {
/// Returns the explicitly set boolean value, or `None`
///
/// ```
/// use tor_config::BoolOrAuto;
///
/// fn calculate_default() -> bool { //...
/// # false }
/// let bool_or_auto: BoolOrAuto = // ...
/// # Default::default();
/// let _: bool = bool_or_auto.as_bool().unwrap_or_else(|| calculate_default());
/// ```
pub fn as_bool(self) -> Option<bool> {
match self {
BoolOrAuto::Auto => None,
BoolOrAuto::Explicit(v) => Some(v),
}
}
}
/// How we (de) serialize a [`BoolOrAuto`]
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum BoolOrAutoSerde {
/// String (in snake case)
String(Cow<'static, str>),
/// bool
Bool(bool),
}
impl From<BoolOrAuto> for BoolOrAutoSerde {
fn from(boa: BoolOrAuto) -> BoolOrAutoSerde {
use BoolOrAutoSerde as BoAS;
boa.as_bool()
.map(BoAS::Bool)
.unwrap_or_else(|| BoAS::String("auto".into()))
}
}
/// Boolean or `"auto"` configuration is invalid
#[derive(thiserror::Error, Debug, Clone)]
#[non_exhaustive]
#[error(r#"Invalid value, respected boolean or "auto""#)]
pub struct InvalidBoolOrAuto {}
impl TryFrom<BoolOrAutoSerde> for BoolOrAuto {
type Error = InvalidBoolOrAuto;
fn try_from(pls: BoolOrAutoSerde) -> Result<BoolOrAuto, Self::Error> {
use BoolOrAuto as BoA;
use BoolOrAutoSerde as BoAS;
Ok(match pls {
BoAS::Bool(v) => BoA::Explicit(v),
BoAS::String(s) if s == "false" => BoA::Explicit(false),
BoAS::String(s) if s == "true" => BoA::Explicit(true),
BoAS::String(s) if s == "auto" => BoA::Auto,
_ => return Err(InvalidBoolOrAuto {}),
})
}
}
/// Padding enablement - rough amount of padding requested
///
/// Padding is cover traffic, used to help mitigate traffic analysis,
@ -320,6 +398,9 @@ mod test {
#[derive(Debug, Deserialize, Serialize)]
struct TestConfigFile {
#[serde(default)]
something_enabled: BoolOrAuto,
#[serde(default)]
padding: PaddingLevel,
@ -327,6 +408,32 @@ mod test {
listen: Option<Listen>,
}
#[test]
fn bool_or_auto() {
use BoolOrAuto as BoA;
let chk = |pl, s| {
let tc: TestConfigFile = toml::from_str(s).expect(s);
assert_eq!(pl, tc.something_enabled, "{:?}", s);
};
chk(BoA::Auto, "");
chk(BoA::Auto, r#"something_enabled = "auto""#);
chk(BoA::Explicit(true), r#"something_enabled = true"#);
chk(BoA::Explicit(true), r#"something_enabled = "true""#);
chk(BoA::Explicit(false), r#"something_enabled = false"#);
chk(BoA::Explicit(false), r#"something_enabled = "false""#);
let chk_e = |s| {
let tc: Result<TestConfigFile, _> = toml::from_str(s);
let _ = tc.expect_err(s);
};
chk_e(r#"something_enabled = 1"#);
chk_e(r#"something_enabled = "unknown""#);
chk_e(r#"something_enabled = "True""#);
}
#[test]
fn padding_level() {
use PaddingLevel as PL;