1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! Extends the [dotenvy](https://crates.io/crates/dotenvy) crate with *.env.vault* file support.
//! The extended standard lets you load encrypted secrets from your *.env.vault* file in production (and other) environments.

mod errors;
mod log;
mod vault;

pub use dotenvy;
pub use errors::Error;

use errors::Result;
use vault::Vault;

/// Loads the *.env.vault* file from [`env::current_dir`](std::env::current_dir) using the *DOTENV_KEY* environment
/// variable.
///
/// If the key or vault cannot be found, a regular *.env* file is loaded instead.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your *.env*
/// file, the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your *.env.vault* file, ignoring variables
/// already existing in the environment, then use [`dotenv_override`] instead.
///
/// An error will be returned if the file is not found.
///
/// # Examples
/// ```no_run
/// fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
///     dotenv_vault::dotenv()?;
///     Ok(())
/// }
/// ```
pub fn dotenv() -> Result<()> {
    Vault::new().load()
}

/// Loads all variables into the environment, overriding any existing environment variables of the
/// same name.
///
/// If the key or vault cannot be found, a regular *.env* file is loaded instead.
///
/// Where multiple declarations for the same environment variable exist in your *.env* file, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`dotenv`] instead.
///
/// # Examples
/// ```no_run
/// fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
///     dotenv_vault::dotenv_override()?;
///     Ok(())
/// }
/// ```
pub fn dotenv_override() -> Result<()> {
    Vault::new().load_override()
}

#[cfg(test)]
mod tests {
    use serial_test::serial;
    use std::{env, fs::File, io::prelude::*};
    use tempfile::tempdir;

    #[test]
    #[serial] // Run serially due to env modifications
    fn dotenv_ok() {
        env::set_var("DOTENV_KEY", "dotenv://:key_ddcaa26504cd70a6fef9801901c3981538563a1767c297cb8416e8a38c62fe00@dotenv.local/vault/.env.vault?environment=production");

        let tmp = tempdir().unwrap();
        let vault_path = tmp.path().join(".env.vault");
        let mut vault = File::create(&vault_path).unwrap();
        vault
            .write_all("DOTENV_VAULT_PRODUCTION=\"s7NYXa809k/bVSPwIAmJhPJmEGTtU0hG58hOZy7I0ix6y5HP8LsHBsZCYC/gw5DDFy5DgOcyd18R\"".as_bytes())
            .unwrap();
        vault.sync_all().unwrap();

        let cwd = env::current_dir().unwrap();
        env::set_current_dir(&tmp).unwrap();

        let result = super::dotenv();
        assert!(result.is_ok());

        let from_vault = env::var("ALPHA");
        assert!(from_vault.is_ok());
        assert!(from_vault.unwrap() == "zeta");

        tmp.close().unwrap();
        env::remove_var("DOTENV_KEY");
        env::remove_var("ALPHA");
        env::set_current_dir(cwd).unwrap();
    }

    #[test]
    #[serial] // Run serially due to env modifications
    fn dotenv_fallback_to_env() {
        let tmp = tempdir().unwrap();
        let env_path = tmp.path().join(".env");
        let mut env = File::create(&env_path).unwrap();
        env.write_all("TESTKEY=\"from .env\"".as_bytes()).unwrap();
        env.sync_all().unwrap();

        let cwd = env::current_dir().unwrap();
        env::set_current_dir(&tmp).unwrap();

        let result = super::dotenv();
        assert!(result.is_ok());

        let from_env = env::var("TESTKEY");
        assert!(from_env.is_ok());
        assert!(from_env.unwrap() == "from .env");

        tmp.close().unwrap();
        env::remove_var("TESTKEY");
        env::set_current_dir(cwd).unwrap();
    }

    #[test]
    #[serial] // Run serially due to env modifications
    fn dotenv_override_ok() {
        env::set_var("DOTENV_KEY", "dotenv://:key_ddcaa26504cd70a6fef9801901c3981538563a1767c297cb8416e8a38c62fe00@dotenv.local/vault/.env.vault?environment=production");

        let tmp = tempdir().unwrap();
        let vault_path = tmp.path().join(".env.vault");
        let mut vault = File::create(&vault_path).unwrap();
        vault
            .write_all("DOTENV_VAULT_PRODUCTION=\"s7NYXa809k/bVSPwIAmJhPJmEGTtU0hG58hOZy7I0ix6y5HP8LsHBsZCYC/gw5DDFy5DgOcyd18R\"".as_bytes())
            .unwrap();
        vault.sync_all().unwrap();

        let cwd = env::current_dir().unwrap();
        env::set_current_dir(&tmp).unwrap();

        env::set_var("ALPHA", "beta");

        let result = super::dotenv_override();
        assert!(result.is_ok());

        let from_vault = env::var("ALPHA");
        assert!(from_vault.is_ok());
        assert!(from_vault.unwrap() == "zeta");

        tmp.close().unwrap();
        env::remove_var("DOTENV_KEY");
        env::remove_var("ALPHA");
        env::set_current_dir(cwd).unwrap();
    }

    #[test]
    #[serial] // Run serially due to env modifications
    fn dotenv_override_fallback_to_env() {
        let tmp = tempdir().unwrap();
        let env_path = tmp.path().join(".env");
        let mut env = File::create(&env_path).unwrap();
        env.write_all("TESTKEY=\"from .env\"".as_bytes()).unwrap();
        env.sync_all().unwrap();

        let cwd = env::current_dir().unwrap();
        env::set_current_dir(&tmp).unwrap();

        env::set_var("TESTKEY", "helloworld");

        let result = super::dotenv_override();
        assert!(result.is_ok());

        let from_env = env::var("TESTKEY");
        assert!(from_env.is_ok());
        assert!(from_env.unwrap() == "from .env");

        tmp.close().unwrap();
        env::remove_var("TESTKEY");
        env::set_current_dir(cwd).unwrap();
    }
}