leo_package/
env.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::NetworkName;
18
19use leo_errors::{CliError, PackageError, Result};
20
21use std::{fmt, fs, path::Path};
22
23pub const ENV_FILENAME: &str = ".env";
24
25#[derive(Clone, Debug)]
26pub struct Env {
27    pub network: NetworkName,
28    pub private_key: String,
29    pub endpoint: String,
30}
31
32impl Env {
33    pub fn new(network: NetworkName, private_key: String, endpoint: String) -> Self {
34        Env { network, private_key, endpoint }
35    }
36
37    pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), PackageError> {
38        let contents = self.to_string();
39        fs::write(path, contents).map_err(PackageError::io_error_env_file)
40    }
41
42    pub fn read_from_file_or_environment<P: AsRef<Path>>(path: P) -> Result<Self> {
43        // Read the `.env` file from the given directory.
44        // If the file does not exist, then attempt to read it from its parent directory recursively until
45        // there are no more parent directories.
46        let path = path.as_ref().to_path_buf();
47        let mut contents = String::new();
48        let mut current_path = path;
49        while current_path.exists() {
50            let env_path = current_path.join(ENV_FILENAME);
51            if env_path.exists() {
52                contents = fs::read_to_string(env_path).map_err(PackageError::io_error_env_file)?;
53                break;
54            }
55            current_path = match current_path.parent() {
56                Some(parent) => parent.to_path_buf(),
57                None => break,
58            };
59        }
60
61        let mut network: Option<String> = None;
62        let mut private_key: Option<String> = None;
63        let mut endpoint: Option<String> = None;
64
65        for line in contents.lines() {
66            if let Some((lhs, rhs)) = line.split_once('=') {
67                match lhs.trim() {
68                    "NETWORK" => network = Some(rhs.to_string()),
69                    "PRIVATE_KEY" => private_key = Some(rhs.to_string()),
70                    "ENDPOINT" => endpoint = Some(rhs.to_string()),
71                    _ => {}
72                }
73            }
74        }
75
76        for (variable, name) in
77            [(&mut network, "NETWORK"), (&mut private_key, "PRIVATE_KEY"), (&mut endpoint, "ENDPOINT")]
78        {
79            if let Ok(env_var_value) = std::env::var(name) {
80                if !env_var_value.is_empty() {
81                    *variable = Some(env_var_value);
82                }
83            }
84        }
85
86        let network: Option<NetworkName> = network.and_then(|net| net.parse().ok());
87
88        match (network, private_key, endpoint) {
89            (Some(network), Some(private_key), Some(endpoint)) => Ok(Env { network, private_key, endpoint }),
90            (None, _, _) => Err(CliError::failed_to_get_network_from_env().into()),
91            (_, None, _) => Err(CliError::failed_to_get_private_key_from_env().into()),
92            (_, _, None) => Err(CliError::failed_to_get_endpoint_from_env().into()),
93        }
94    }
95}
96
97impl fmt::Display for Env {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(f, "NETWORK={}\nPRIVATE_KEY={}\nENDPOINT={}\n", self.network, self.private_key, self.endpoint)
100    }
101}