dotenvy icon indicating copy to clipboard operation
dotenvy copied to clipboard

`EnvLoader::new().load()?` should not fail even if `.env` file does not present.

Open simnalamburt opened this issue 1 year ago • 5 comments

It should proceed and silently defaults to std::env::vars, so that users can use dotenvy without .env file. Almost all dotenv implementations in various programming languages function normally even without a .env file.

simnalamburt avatar Oct 09 '24 21:10 simnalamburt

This has always been the default behaviour of dotenv/dotenvy.

v0.15 or earlier

fn main() {
    dotenvy::dotenv().unwrap();
}
thread 'main' panicked at src/main.rs:2:23:
called `Result::unwrap()` on an `Err` value: Io(Custom { kind: NotFound, error: "path not found" })

The new EnvLoader can be used without an env file by configuring EnvSequence.

allan2 avatar Oct 09 '24 22:10 allan2

I wrote the code below, but there was no option to make the .env file optional.

use dotenvy::{EnvLoader, EnvSequence};

fn main() {
    EnvLoader::new().sequence(EnvSequence::EnvOnly).load().unwrap(); // Not panics
    EnvLoader::new().sequence(EnvSequence::EnvThenInput).load().unwrap(); // Panics
    EnvLoader::new().sequence(EnvSequence::InputThenEnv).load().unwrap(); // Panics
    EnvLoader::new().sequence(EnvSequence::InputOnly).load().unwrap(); // Panics
    println!("Hello, world!");
}

This seems unintuitive from the user’s perspective. The dotenvy-macros crate has an option called "required" that makes the .env file optional. What do you think about adding a "required" option to dotenvy as well?

// sample usage
let env = EnvLoader::new().required(false).load()?;
References
  • https://github.com/allan2/dotenvy/blob/e2da11066/dotenvy-macros/src/lib.rs#L40

simnalamburt avatar Oct 11 '24 13:10 simnalamburt

EnvSequence expresses more than just required. It is more like:

loader.required(false).existing_env(true).override(false)

Having the configuration as a type is nice. It allows for this --> dev/prod example

The config syntax of [#load] being different is an issue though. I am thinking to have [#load] take a singular sequence parameter, aligning it with EnvSequence, and adding more documentation.

Thoughts?

allan2 avatar Oct 11 '24 14:10 allan2

loader.required(false).existing_env(true).override(false)

Having the configuration as a type is nice.

Cool. It seems like this would allow for all possible cases to be expressed, and it would greatly increase user flexibility as well.

I am thinking to have [#load] take a singular sequence parameter, aligning it with EnvSequence, and adding more documentation.

It sounds good, but the types of values that can be passed as parameters within an attribute are extremely limited, so I’m not sure if it’s feasible to implement. If it’s possible, it would indeed be great.

simnalamburt avatar Oct 15 '24 09:10 simnalamburt

I've temporarily solved it this way:

fn load_env_vars(project_root: &PathBuf) -> Result<EnvMap> {
    let env_map = match EnvLoader::with_path(project_root.join(".env"))
        .sequence(EnvSequence::InputThenEnv)
        .load()
    {
        Ok(env_map) => env_map,
        // It'll fail if .env is missing, so we return a map with std::env only
        Err(dotenvy::Error::Io(_, _)) => EnvLoader::new()
            .sequence(EnvSequence::EnvOnly)
            .load()
            .context("Failed loading environment variables")?,
        err => err.context("Failed loading .env file")?,
    };
    Ok(env_map)
}

DZakh avatar Nov 20 '24 10:11 DZakh