confgr_derive 0.2.1

Procedural derive macro implementation for the confgr crate
Documentation
use crate::{ConfigAttributes, SUFFIX};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;

pub(crate) fn generate_from_file(name: &Ident, attributes: &ConfigAttributes) -> TokenStream {
    let layer_name = format_ident!("{}{}", name, SUFFIX);

    if attributes.path.is_some() && attributes.default_path.is_some() {
        panic!("'path' and 'default_path' attributes cannot be used alongside eachother");
    }

    let get_file_path_def = if let Some(env_path) = &attributes.env_path {
        match (&attributes.path, &attributes.default_path) {
            (Some(path), None) => {
                quote! {
                    fn get_file_path() -> Option<String> {
                        match std::env::var(#env_path) {
                            Ok(env_val) => if std::path::Path::new(&env_val).exists() { Some(env_val) }
                                           else if std::path::Path::new(#path).exists() { Some(#path.to_string()) }
                                           else { panic!("'env_path' and 'path' attributes resolve to non-existent or invalid files.") },
                            Err(_) => if std::path::Path::new(#path).exists() { Some(#path.to_string()) }
                                      else { panic!("'env_path' variable is not set and the provided 'path' attribute is invalid or references a non-existent file.") }
                        }
                    }
                }
            }
            (None, Some(default_path)) => {
                quote! {
                    fn get_file_path() -> Option<String> {
                        match std::env::var(#env_path) {
                            Ok(env_val) => if std::path::Path::new(&env_val).exists() { Some(env_val) }
                                           else if std::path::Path::new(#default_path).exists() { Some(#default_path.to_string()) }
                                           else { None },
                            Err(_) => if std::path::Path::new(#default_path).exists() { Some(#default_path.to_string()) }
                                      else { None }
                        }
                    }
                }
            }
            _ => {
                quote! {
                    fn get_file_path() -> Option<String> {
                        std::env::var(#env_path)
                            .map(|env_val| {
                                if std::path::Path::new(&env_val).exists() {
                                    Some(env_val)
                                } else {
                                    None
                                }
                        })
                        .ok()
                        .flatten()
                    }
                }
            }
        }
    } else if let Some(path) = &attributes.path {
        quote! {
            fn get_file_path() -> Option<String> {
                if std::path::Path::new(#path).exists() {
                    Some(#path.to_string())
                } else {
                    panic!("The provided 'path' attribute value '{}' is invalid or references a non-existent file.", #path);
                }
            }
        }
    } else if let Some(default_path) = &attributes.default_path {
        quote! {
            fn get_file_path() -> Option<String> {
                if std::path::Path::new(#default_path).exists() {
                    Some(#default_path.to_string())
                } else {
                    None
                }
            }
        }
    } else {
        quote! {
            fn get_file_path() -> Option<String> { None }
        }
    };

    quote! {
        #[automatically_derived]
        impl ::confgr::core::FromFile for #layer_name {
            #get_file_path_def

            fn from_file() -> Result<Self, ::confgr::core::ConfgrError> {
                let file_path = Self::get_file_path().ok_or(::confgr::core::ConfgrError::NoFilePath)?;

                Self::check_file()?;

                let config = ::confgr::config::Config::builder()
                    .add_source(::confgr::config::File::with_name(&file_path))
                    .build()?;

                config.try_deserialize::<Self>().map_err(|e| ::confgr::core::ConfgrError::Config(e))
            }

            fn check_file() -> Result<(), ::confgr::core::ConfgrError> {
                use ::std::fs::File;
                use ::std::io::Read;

                let file_path = Self::get_file_path().ok_or(::confgr::core::ConfgrError::NoFilePath)?;

                let mut file = File::open(&file_path)
                    .map_err(|e| ::confgr::core::ConfgrError::File(e))?;

                let mut contents = String::new();
                file.read_to_string(&mut contents)
                    .map_err(|e| ::confgr::core::ConfgrError::File(e))?;


                Ok(())
            }
        }
    }
}