Macro config_namespace

Source
macro_rules! config_namespace {
    (
        $(#[doc = $struct_d:tt])* // Struct-level documentation attributes
        $(#[deprecated($($struct_depr:tt)*)])? // Optional struct-level deprecated attribute
        $(#[allow($($struct_de:tt)*)])?
        $vis:vis struct $struct_name:ident {
            $(
                $(#[doc = $d:tt])* // Field-level documentation attributes
                $(#[deprecated($($field_depr:tt)*)])? // Optional field-level deprecated attribute
                $(#[allow($($field_de:tt)*)])?
                $field_vis:vis $field_name:ident : $field_type:ty,
                $(warn = $warn:expr,)?
                $(transform = $transform:expr,)?
                default = $default:expr
            )*$(,)*
        }
    ) => { ... };
}
Expand description

A macro that wraps a configuration struct and automatically derives Default and ConfigField for it, allowing it to be used in the ConfigOptions configuration tree.

transform is used to normalize values before parsing.

For example,

config_namespace! {
   /// Amazing config
   pub struct MyConfig {
       /// Field 1 doc
       field1: String, transform = str::to_lowercase, default = "".to_string()

       /// Field 2 doc
       field2: usize, default = 232

       /// Field 3 doc
       field3: Option<usize>, default = None
   }
}

Will generate

/// Amazing config
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct MyConfig {
    /// Field 1 doc
    field1: String,
    /// Field 2 doc
    field2: usize,
    /// Field 3 doc
    field3: Option<usize>,
}
impl ConfigField for MyConfig {
    fn set(&mut self, key: &str, value: &str) -> Result<()> {
        let (key, rem) = key.split_once('.').unwrap_or((key, ""));
        match key {
            "field1" => {
                let value = str::to_lowercase(value);
                self.field1.set(rem, value.as_ref())
            },
            "field2" => self.field2.set(rem, value.as_ref()),
            "field3" => self.field3.set(rem, value.as_ref()),
            _ => _internal_err!(
                "Config value \"{}\" not found on MyConfig",
                key
            ),
        }
    }

    fn visit<V: Visit>(&self, v: &mut V, key_prefix: &str, _description: &'static str) {
        let key = format!("{}.field1", key_prefix);
        let desc = "Field 1 doc";
        self.field1.visit(v, key.as_str(), desc);
        let key = format!("{}.field2", key_prefix);
        let desc = "Field 2 doc";
        self.field2.visit(v, key.as_str(), desc);
        let key = format!("{}.field3", key_prefix);
        let desc = "Field 3 doc";
        self.field3.visit(v, key.as_str(), desc);
    }
}

impl Default for MyConfig {
    fn default() -> Self {
        Self {
            field1: "".to_string(),
            field2: 232,
            field3: None,
        }
    }
}

NB: Misplaced commas may result in nonsensical errors