declarative-env 0.1.1

This crate parses and populates a struct with the environmental variables defined in a file, for having the documentation and the code always synchronized.
Documentation
use hierrorchy::{error_leaf, error_node};
use std::{error::Error, str::FromStr};
use syn::{parse::Parse, Error as SynError, Ident, LitStr, Token};

pub struct MacroConfig {
    path: String,
    format: AcceptedFormat,
}

impl MacroConfig {
    pub fn path(&self) -> &str {
        &self.path
    }

    pub fn format(&self) -> AcceptedFormat {
        self.format
    }
}

impl Parse for MacroConfig {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let mut macro_config_builder = MacroConfigBuilder::new();
        while !input.is_empty() {
            let keyword: Ident = input.parse()?;
            match keyword
                .to_string()
                .parse::<MacroConfigKeyword>()
                .map_err(|it| SynError::new(keyword.span(), it))?
            {
                MacroConfigKeyword::Path => {
                    let _: Token![=] = input.parse()?;
                    let value: LitStr = input.parse()?;
                    if !input.is_empty() {
                        let _: Token![,] = input.parse()?;
                    }
                    macro_config_builder.set_path(value.value());
                }
                MacroConfigKeyword::Format => {
                    let _: Token![=] = input.parse()?;
                    let value: LitStr = input.parse()?;
                    if !input.is_empty() {
                        let _: Token![,] = input.parse()?;
                    }
                    macro_config_builder.set_format(
                        value
                            .value()
                            .parse::<AcceptedFormat>()
                            .map_err(|it| SynError::new(value.span(), it))?,
                    );
                }
            }
        }
        macro_config_builder
            .build()
            .map_err(|it| SynError::new(input.span(), it))
    }
}

struct MacroConfigBuilder {
    path: Option<String>,
    format: Option<AcceptedFormat>,
}

impl MacroConfigBuilder {
    pub fn new() -> Self {
        MacroConfigBuilder {
            path: None,
            format: None,
        }
    }

    pub fn set_path(&mut self, path: String) {
        self.path = Some(path);
    }

    pub fn set_format(&mut self, format: AcceptedFormat) {
        self.format = Some(format);
    }

    pub fn build(&self) -> Result<MacroConfig, MacroConfigBuilderError> {
        if self.path.is_none() {
            return Err(MissingRequiredConfigurationError {
                keyword: String::from("path"),
            }
            .into());
        }
        Ok(MacroConfig {
            path: self
                .path
                .as_ref()
                .expect("path existence is already checked")
                .clone(),
            format: self.format.unwrap_or(AcceptedFormat::Hjson),
        })
    }
}

error_node! {
    pub type MacroConfigBuilderError<MissingRequiredConfigurationError> = "failed to build macro configuration"
}

#[error_leaf(format!("the required configuration '{}' is missing", self.keyword))]
pub struct MissingRequiredConfigurationError {
    keyword: String,
}

#[derive(Debug, Clone, Copy)]
enum MacroConfigKeyword {
    Path,
    Format,
}

impl FromStr for MacroConfigKeyword {
    type Err = UnknownOptionError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "path" => Ok(Self::Path),
            "format" => Ok(Self::Format),
            _ => Err(UnknownOptionError {
                keyword: s.to_string(),
            }),
        }
    }
}

#[error_leaf(format!("unknown option '{}'", self.keyword))]
pub struct UnknownOptionError {
    keyword: String,
}

#[derive(Debug, Clone, Copy)]
pub enum AcceptedFormat {
    Hjson,
}

impl FromStr for AcceptedFormat {
    type Err = InvalidFormatError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "hjson" => Ok(Self::Hjson),
            _ => Err(InvalidFormatError {
                format: s.to_string(),
            }),
        }
    }
}

#[error_leaf(format!("invalid format '{}'", self.format))]
pub struct InvalidFormatError {
    format: String,
}