direnv.rs

 1use crate::environment::EnvironmentErrorMessage;
 2use std::process::ExitStatus;
 3
 4use {collections::HashMap, std::path::Path, util::ResultExt};
 5
 6#[derive(Clone)]
 7pub enum DirenvError {
 8    NotFound,
 9    FailedRun,
10    NonZeroExit(ExitStatus, Vec<u8>),
11    InvalidJson,
12}
13
14impl From<DirenvError> for Option<EnvironmentErrorMessage> {
15    fn from(value: DirenvError) -> Self {
16        match value {
17            DirenvError::NotFound => None,
18            DirenvError::FailedRun | DirenvError::NonZeroExit(_, _) => {
19                Some(EnvironmentErrorMessage(String::from(
20                    "Failed to run direnv. See logs for more info",
21                )))
22            }
23            DirenvError::InvalidJson => Some(EnvironmentErrorMessage(String::from(
24                "Direnv returned invalid json. See logs for more info",
25            ))),
26        }
27    }
28}
29
30pub async fn load_direnv_environment(
31    env: &HashMap<String, String>,
32    dir: &Path,
33) -> Result<HashMap<String, Option<String>>, DirenvError> {
34    let Ok(direnv_path) = which::which("direnv") else {
35        return Err(DirenvError::NotFound);
36    };
37
38    let args = &["export", "json"];
39    let Some(direnv_output) = smol::process::Command::new(&direnv_path)
40        .args(args)
41        .envs(env)
42        .env("TERM", "dumb")
43        .current_dir(dir)
44        .output()
45        .await
46        .log_err()
47    else {
48        return Err(DirenvError::FailedRun);
49    };
50
51    if !direnv_output.status.success() {
52        log::error!(
53            "Loading direnv environment failed ({}), stderr: {}",
54            direnv_output.status,
55            String::from_utf8_lossy(&direnv_output.stderr)
56        );
57        return Err(DirenvError::NonZeroExit(
58            direnv_output.status,
59            direnv_output.stderr,
60        ));
61    }
62
63    let output = String::from_utf8_lossy(&direnv_output.stdout);
64    if output.is_empty() {
65        // direnv outputs nothing when it has no changes to apply to environment variables
66        return Ok(HashMap::default());
67    }
68
69    match serde_json::from_str(&output) {
70        Ok(env) => Ok(env),
71        Err(err) => {
72            log::error!(
73                "json parse error {}, while parsing output of `{} {}`:\n{}",
74                err,
75                direnv_path.display(),
76                args.join(" "),
77                output
78            );
79            Err(DirenvError::InvalidJson)
80        }
81    }
82}