envcast 1.0.0

Short, clear description of what the project does
Documentation
mod dotenv;

use std::collections::HashMap;
use std::env;
use std::str::FromStr;

use log::{debug, warn};

fn get_from_envvar<T>(var_name: &str) -> Option<T>
where
    T: FromStr,
{
    match env::var(var_name) {
        Ok(env_value) => match T::from_str(&env_value) {
            Ok(value) => Some(value),
            Err(_) => {
                warn!(
                    "Casting from environment variable `{}` is failed!",
                    var_name
                );
                None
            }
        },
        Err(_) => {
            let var_name_upper = var_name.to_uppercase();
            debug!(
                "environment variable `{}` is not set! Trying `{}`",
                var_name, &var_name_upper
            );
            match env::var(&var_name_upper) {
                Ok(env_value) => match T::from_str(&env_value) {
                    Ok(value) => Some(value),
                    Err(_) => {
                        warn!(
                            "Casting from environment variable `{}` is failed!",
                            &var_name_upper
                        );
                        None
                    }
                },
                Err(_) => {
                    debug!("environment variable `{}` is not set!", &var_name_upper);
                    None
                }
            }
        }
    }
}

fn get_from_dotenv<T>(var_name: &str, dotenv_values: &Option<HashMap<String, String>>) -> Option<T>
where
    T: FromStr,
{
    match dotenv_values {
        Some(dotenv) => match dotenv.get(var_name) {
            Some(value) => match T::from_str(value) {
                Ok(value) => Some(value),
                Err(_) => {
                    warn!("Casting from dotenv file value `{}` is failed!", var_name);
                    None
                }
            },
            None => {
                let var_name_upper = var_name.to_uppercase();
                debug!(
                    "Dotenv file variable `{}` is not set! Trying {}",
                    var_name, &var_name_upper
                );
                match dotenv.get(var_name_upper.as_str()) {
                    Some(value) => match T::from_str(value) {
                        Ok(value) => Some(value),
                        Err(_) => {
                            warn!(
                                "Casting from dotenv file value `{}` is failed!",
                                var_name_upper
                            );
                            None
                        }
                    },
                    None => {
                        debug!("Dotenv file variable `{}` is not set!", &var_name_upper);
                        None
                    }
                }
            }
        },
        None => None,
    }
}

/// Retrieves a value for a given field from environment variables, `dotenv_values` parameter, or a default value.
///
/// The function follows a prioritized lookup:
/// 1. Tries to get the value from environment variables.
/// 2. If not found, tries to get it from the provided `dotenv_values` values.
/// 3. If still not found, uses the provided `default_value`.
/// 4. Finally, if `default_value` is `None`, falls back to `T::default()`.
///
/// # Type Parameters
///
/// - `T`: The type of the value to retrieve. Must implement `FromStr` and `Default`.
///
/// # Parameters
///
/// - `field_name`: The name of the field to look up in environment variables or `dotenv_values`.
/// - `default_value`: Optional default value to use if no environment or `dotenv_values` value is found.
/// - `dotenv_values`: Optional reference to a `HashMap` representing `.env` key-value pairs.
///
/// # Returns
///
/// The resolved value of type `T`.
///
/// # Note
///
/// This function is used internally by the code generated by the `FromEnv` derive macro.
pub fn get_from_env<T>(
    field_name: &str,
    default_value: Option<T>,
    dotenv_values: &Option<HashMap<String, String>>,
) -> T
where
    T: FromStr + Default,
{
    if let Some(envvar_value) = get_from_envvar::<T>(field_name) {
        return envvar_value;
    }
    if let Some(dotenv_value) = get_from_dotenv::<T>(field_name, dotenv_values) {
        return dotenv_value;
    }
    let mut final_value = T::default();

    if let Some(actual_value) = default_value {
        final_value = actual_value;
    }

    final_value
}

extern crate envcast_derive;
pub use dotenv::get_dotenv_file_values;
pub use envcast_derive::FromEnv;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_from_envvar_case_success() {
        env::set_var("some_var", "value");

        assert_eq!(get_from_envvar("some_var"), Some("value".to_string()));

        env::remove_var("some_var");
    }

    #[test]
    fn test_get_from_envvar_case_fail_cast() {
        env::set_var("some_var", "value");

        assert_eq!(get_from_envvar::<i32>("some_var"), None);

        env::remove_var("some_var");
    }

    #[test]
    fn test_get_from_envvar_case_fail_and_retry_upper() {
        env::set_var("SOME_VAR", "value");

        assert_eq!(get_from_envvar("some_var"), Some("value".to_string()));

        env::remove_var("SOME_VAR");
    }

    #[test]
    fn test_get_from_envvar_case_retry_upper_and_fail_cast() {
        env::set_var("SOME_VAR", "value");

        assert_eq!(get_from_envvar::<i32>("some_var"), None);

        env::remove_var("SOME_VAR");
    }
}